***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************
六、继承与面向对象设计
Inheritance and Object-Oriented Design
条款33 : 避免遮掩继承而来的名称
rule 33 : Avoid hiding inherited names
1.关于名称,无关于继承,而关于和作用域有关
>1 local and global
int x; // global变量 void someFunc() { double x; // local变量 std::cin>>x; // 读一个值,赋给local变量 }
就如,上面代码作用域的形势是:
当编译器处于someFunc的作用域内并遭遇名称x时,它在local作用域内查找是否有什么东西带着这个名称。
如果找到,就不再查找这个作用域,
如果找不到,向更大范围查找。
可以发现,本例的 someFunc 中x 是 double类型,但global x 是 int类型,
C++的名称遮掩规则所做的唯一事情就是:遮掩名称。(对于名称的类型,并不重要)
>2 继承
* 例1
当位于一个derived class成员函数内指涉base class内的某物(或许 成员函数、或许 typedef、或许 成员变量)时,编译器可以找出我们指涉的东西,因为derived class继承了声明于base class的所有东西,
其实,它们的样子是这样的:
class Base { private: int x; public: virtual void mf1() = 0; virtual void mf2(); void mf3(); ... }; class Derived : public Base { public: virtual void mf1(); void mf4(); ... };
这个例子中,有很多东西,其实都只是想说明——唯一重要的东西——是 名称 。
至于这些东西是private,public,函数,virtual什么的都不重要!
而且,这里用的 单一继承,把这个搞懂,多重继承行为,也就出来了。
假设 derived class 内的mf4的实现码部分像这样:
void Derived::mf4() { ... mf2(); ... }
? 查找local作用域(就是mf4函数所覆盖的那部分),没有
? 查找外围作用域,class Derived 作用域,没有
? 再向外扩一轮,base class 作用域,找到, 停止查找,就用它了。
如果,在 base class 作用域,也没有查找到,会继续查找 base class 的namespace作用域,最后找到 global作用域。
*例2
我们将例1的例子改一下,重载mf1和mf3,并添加一个新版mf3到derived。
会如同后面的 条款36 所言,Derived class 重载了 mf3,但这是一个继承而来的non-virtual函数。
(这个例子有些乱,但我们讨论的是 名称可视性,so 其他的暂时不重要)
class Base { private: int x; public: virtual void mf1() = 0; virtual void mf1( int ); virtual void mf2(); void mf3(); void mf3(double); ... }; class Derived : public Base { public: virtual void mf1(); void mf3(); void mf4(); ... };
以作用域为基础的“名称遮掩规则”并没有改变,因此base class内所有名为mf1和mf3的函数都被derived class内的mf1和mf3函数都遮盖掉了。
从名称查找观点来看, Base::mf1 和 Base::mf3 不再被 Derived继承:
Derived d; int x; ... d.mf1(); // 没问题,调用Derived::mf1 d.mf1(x); // 错误! 因为Derived::mf1遮掩了Base::mf1 d.mf2(); // 没问题,调用Base::mf2 d.mf3(); // 没问题,调用Derived::mf3 d.mf3(x); // 错误! 因为Derived::mf3遮掩了Base::mf3
这个例子说明了,即使 base class 和 derived class 内的函数有不同的参数类型也都适用,而且不论函数是virtual 或者 non-virtual 一体适用。
2. 原因
基本理由是 为了防止你在程序库或应用框架内建立新的derived class时附带地从疏远的base class继承重载函数。
不幸的是,你一直想继承重载函数...
实际上 如果你正在适用public继承而又不继承重载函数,就是违反base 和 derivedclass之间的 is-a 关系,而条款32说过 is-a 是public继承的基石。
3.解决
① 在 public 继承中
你可以使用 using声明式 达成目标:
class Base { private: int x; public: virtual void mf1() = 0; virtual void mf1(int); virtual void mf2(); void mf3(); void mf3(double); ... }; class Derived : public Base { public: using Base::mf1; // 让 Base class内名为mf1和mf3的所有东西在Derived 作用域都可见 using Base::mf3; virtual void mf1(); void mf3(); void mf4(); ... };
现在,之前的那些行为都可以执行了。
这意味如果你继承base class并加上重载函数,而你又希望重新定义或覆写其中一部分,那么你必须为那些原本会被遮掩的每个名称引入一个using声明式,否则其他东西会被覆盖掉。
② 在 private 继承
有时候,并不想继承base class的所有函数,在public 继承中,这绝对不可能发生,因为违反了 is-a 关系。
然而在private继承中,它却可以发生。
例如,假设Derived以private形式继承Base,而Derived唯一想继承的mf1是那个无参数版本。
using声明式在这里排不上用场,因为using声明式会令继承而来的某给定名称之所有同名函数在derived class中都可见。
所以,我们需要一个 转交函数(forwarding function):
class Base { public: virtual void mf1() = 0; virtual void mf1(int); ... // 与之前相同 }; class Derived : private Base { public: virtual void mf1() // 转交函数,暗自成为inline(原因,见条款30) { Base::mf1(); } ... }; ... Derived d; int x; d.mf1(); // 很好,调用的是Derived::mf1 d.mf1(x); // 错误! Base::mf1被遮掩了
4. 总结
这就是继承和名称遮掩的完整例子。
但是当继承结合 template,我们又将面对 "继承名称被遮掩" 的一个全然不同的形式。关于 " 以角括号定界 " 的所有东西,详见条款43。
请记住
? derived class 内的名称会遮掩base class内的名称。在public继承下从来没人希望如此。
? 为了让被遮掩的名称再见天日,可使用 using声明式 或 转交函数。
***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************
原文地址:http://blog.csdn.net/lttree/article/details/46621949