码迷,mamicode.com
首页 > 编程语言 > 详细

《Effective C++》学习笔记——条款33

时间:2015-06-24 19:08:24      阅读:126      评论:0      收藏:0      [点我收藏+]

标签:effective c++   学习笔记   条款33   

***************************************转载请注明出处: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变量
}

这个读取数据的语句指涉的是local变量x,而不是global变量x,因为内层作用域的名称会遮掩外围作用域的名称。

就如,上面代码作用域的形势是:

技术分享

当编译器处于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();
  ...
}

当编译器看到使用名称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被遮掩了

inline转交函数的另一个用途是为那些不支持using声明式的老旧编译器的一条新路,将继承而得的名称汇入derived class作用域内。




4. 总结

这就是继承和名称遮掩的完整例子。

但是当继承结合 template,我们又将面对 "继承名称被遮掩" 的一个全然不同的形式。关于 " 以角括号定界 " 的所有东西,详见条款43。


请记住

? derived class 内的名称会遮掩base class内的名称。在public继承下从来没人希望如此。

? 为了让被遮掩的名称再见天日,可使用 using声明式 或 转交函数。






***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************

《Effective C++》学习笔记——条款33

标签:effective c++   学习笔记   条款33   

原文地址:http://blog.csdn.net/lttree/article/details/46621949

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!