Skip to content

Latest commit

 

History

History
155 lines (134 loc) · 3.94 KB

File metadata and controls

155 lines (134 loc) · 3.94 KB

条款33:避免遮蔽继承而来的名称

这个题材和继承其实无关,而是和作用域有关。我们都知道在下面的代码中:

int x;  // global
void someFunc() {
  double x; // local
  std::cin >> x;
}

这个读取数据的语句指的是local变量x,而不是global变量x。

现在导入继承。我们知道,当位于一个派生类成员函数内使用基类内的某物时,编译器可以找到我们所使用的东西,因为派生类继承了基类内的所有东西。

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();
  ...
};

假设派生类内的mf4的实现部分长这样:

void Derived::mf4() {
  ...
  mf2();
  ...
};

当编译器看到名称mf2时,查找各作用域,看看有没有某个名为mf2的声明式。首先查找local作用域,在那没找到任何名为mf2的东西。于是查找其外围作用域,也就是派生类覆盖的作用域。如果还是没有找到,于是再往外围移动,本例为基类。在那找到一个名为mf2的东西了,于是停止查找。

再次考虑一个例子,这次让我们重载mf1和mf3,并且添加一个新的mf3到派生类区。Derived重载了mf3,那是一个继承而来的non-virtual函数。

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();
  ...
};

基类内所有名为mf1和mf3的函数都被派生类内的mf1和mf3遮蔽掉了。从名字查找来看,Base::mf1和Base::mf3不再被Derived继承!

Derived d;
int x;
...
d.mf1();    // Derived::mf1
d.mf1(x);   // error: no Derived::mf1(int)
d.mf2();    // Base::mf2
d.mf3();    // Derived::mf3
d.mf3(x);   // error: no Derived::mf3(int)

如你所见,上述规则都适用,即使基类和派生类内的函数有不同的参数类型也适用,而且不论函数是virtual还是non-virtual一起适用。

你可以适用 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;
  using Base::mf3;
  virtual void mf1();
  void mf3();
  void mf4();
  ...
};

现在,继承机制将一如既往地运作:

Derived d;
int x;
...
d.mf1();    // Derived::mf1
d.mf1(x);   // Base::mf1(int)
d.mf2();    // Base::mf2
d.mf3();    // Derived::mf3
d.mf3(x);   // Base::mf3(int)

这意味着如果你继承基类并加上重载函数,而你又希望重新定义或重写其中的一部分,那么你必须为那些原本会被遮蔽的每个名字引入一个 using 声明。

有时候你并不想要继承基类的所有函数,但在public继承下,这绝不可能发生,因为它违反了public继承下的is-a关系。然而在private继承下它却可能是有意义的。例如假设Derived以private继承Base,而Derived唯一想继承mf1的无参数版本。 using 声明在这里派不上用场,因为 using 声明会导致继承来的名称在派生类中都可见。我们需要不同的技术,即一个简单的转发函数(forwarding function):

class Base {
private:
  int x;
public:
  virtual void mf1() = 0;
  virtual void mf1(int);
  ... // 与此前相同
};
class Derived : private Base {
public:
  virtual void mf1() {  // forwarding function
    Base::mf1();
  }
  void mf3();
  void mf4();
  ...
};
...
Derived d;
int x;
d.mf1();  // Derived::mf1() 
d.mf1(x); // error: Base::mf1(int)

请记住

  • 派生类内的名称会遮蔽基类内的名称。在public继承下从来没有人希望如此。
  • 为了让被遮蔽的名称可见,可使用 using 声明或转发函数。