《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********************************************

时间: 2024-08-26 13:32:34

《Effective C++》学习笔记——条款33的相关文章

Effective C++学习笔记 条款07:为多态基类声明virtual析构函数

一.C++明确指出:当derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未定义——实际执行时通常发生的是对象的derived成分没有被销毁!(注:使用基类引用派生类的方式使用多态,由于引用只是对原对象的一个引用或者叫做别名,其并没有分配内存,对其引用对象内存的销毁,将由原对象自己负责,所以使用引用的时候,如果析构函数不为virtual,那么也不会发生derived成员没有销毁的情况) 例如: class b

effective c++学习笔记条款23-25

条款23:宁可用非成员,非友元函数来替代成员函数 1.非成员函数提供了更好的封装性,这个函数内不能访问类的私有成员,封装的越严密我们对类的数据就可以弹性越大的操纵,因为可见这些数据的客户越少,反之数据影响的客户也就越少. 2.c++比较自然的做法-(关系到标准库numplace的组织结构),可以把不同便捷函数放到不同Namespace去,让客户来决定要用的非成员函数功能,这是类不能提供的. 条款24:若所有参数皆需类型转换,请为此采用非成员函数. 1.如果你需要为某个函数的所有参数(包括被thi

effective c++学习笔记条款11-13

条款11: 1.令赋值运算符返回一个&,因为STL,string都是这样做的,除非你有足够好的理由不这样做. 2.处理自我赋值的方法----(1).在没有成功获取对象数据时不要删除自己的数据,避免发生异常后原对象指针是一个悬浮指针 (2).判断自我赋值的检查操作会耗费不少时间,可以用swap交换数据技术来优化---(1)形参为赋值而来,(2)形参为静态引用,多加一个函数内拷贝操作.

effective c++学习笔记条款8-10

条款7:为多态基类声明虚析构函数 1.一个基类指针接受一个派生类对象的地址时,对该指针delete,仅仅释放基类部分 2.给所有类都带上虚析构函数是个馊主意,会带有vptr指向一个函数指针数组,扩大不必要的对象大小,除非补偿vptr,否则没有移植性. 3.string类和STL不含有虚析构函数,然而一些用户 却将他们作为基类,运用   delete指向派生类的基类指针,导致错误[c++11添加了禁止派生性质],他们不适合当基类. 4,手头上没有合适的纯虚函数,但你确实需要一个抽象类,把析构函数声

effective c++学习笔记条款20-22

条款20:用引用传递代替值传递 1.尽量以引用传递来代替传值传递,前者比较高效,并且可以避免切割问题 2.以上规则不适用于内置类型,以及STL的迭代器,和函数对象 条款21:必须返回对象时,别妄想返回对象的引用 1.绝对不要返回指针和引用指向一个局部对象或者静态局部对象而有可能需要多个这样的对象,条款4已经为在单线程环境合理返回&指向一个局部静态提供了一份设计实例.(保护初始化顺序) 条款22:将成员变量声明为private 1.切记将成员变量声明为private.这可赋予客户访问数据的一致性,

effective c++学习笔记条款4-7

条款4:确定对象被使用前已经初始化 一. 变量在不同情况下可能会初始化,也可能不会初始化. 注意初始化和赋值的区别. 1.在类中内置类型不会发生隐式初始化,自定义有默认构造函数的能被默认初始化 所以在构造类时务必初始化内置类型,最好给自定义的对象显示初始化避免在函数体中赋值浪费资源. 2.内置类型在函数体内不会初始化,在函数体外自动初始化为0. 二. 1.const和引用类型必须初始化,不可能赋值 三 1.当类实在是有较多构造函数,并且总是要对一些成员数据重复初始化,可以考虑将那些“赋值和初始化

effective c++学习笔记条款17-19

条款17:以独立语句将New对象放置入智能指针. 1.以独立语句将newed对象放置入智能指针内,如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露. void name(shared_ptr<管理对象类型>(new 管理对象类型),其它函数)),New被分配内存不一定马上放入管理对象,因为有其它函数干扰,这不是独立语句. 条款18:让接口容易被正确使用,不易被误用. 1.好的接口很容易被正确使用,不容易被误用.你应该在你的所有接口中努力达成这些性质. 2.“促进正确使用”的办法包括接

effective c++学习笔记条款35-37

#include<iostream> using namespace std; class A { public: void asd() { pri(); } private: /*virtual*/ void pri() { cout << "基类函数" << endl; } }; class B :public A { private: void pri() /*override*/ { cout << "派生类函数&quo

effective c++学习笔记条款29-31

条款29:为异常安全而努力是值得的[回顾] 1.异常安全函数即使发生异常也不会泄露资源或允许任何数据结构败坏,这样的函数分为3种可能的保证:基本型,强烈型,不抛异常型 2.“强烈保证”往往能通过copying and swap 来实现出来,但并非所有函数都可实现或者具备现实意义. 3.函数提供的“异常安全保证”通常最高只等于其所调用的各个函数的“异常安全中”的最弱者.