一. 内容
-
这个主题其实与
作用域(Scope)
有关。举个例子:int x=1; //全局变量 void func() { double x=1.1; //局部变量 std::cout<<x<<"\n"; }
当编译器在 func 的作用域并使用 x 时,它会
先在 local 作用域查找是否存在该变量,如果找不到在扩大作用域
。显然,编译器会在 local 找到 double x,然后停止查找,这意味着在 local 中使用 x,将总是找到 double x,而非 global 作用域的 int x
。这便是我们所说的:遮掩名称。 -
谈到继承,继承的作用域机制也类似上面的例子,使用变量或者函数时,编译器会先
查找 derived class 作用域,寻找失败再扩大范围到 base class 作用域。注意一点,编译器只会查找是否名称相同,而不在乎其变量类型或者参数类型
。只有当在同一作用域寻找到多个的情况下才考虑根据参数类型调用哪个函数比较合适,当存在多个合适的函数,就会抛出二义性的编译异常
。 -
为了充分认识到遮掩名称带来的危害,举个例子:
class Base { public: virtual void mf1()=0; virtual void mf1(int); virtual void mf2(); void mf3(); void mf3(double); private: }; class Derived : public Base { public: virtual void mf1(); void mf3(); }; inline void Try() { Derived d; int x; d.mf1(); d.mf1(x); //报错,因为 derived:: mf1 遮掩了 base:: mf1 (int) d.mf2(); d.mf3(); d.mf3(4); //报错,因为 derived::mf3 遮掩了 base::mf3 (double) }
这段代码的行为会让每一位面对它的 C++ 程序员大吃一惊,以作用域为基础的名称遮掩规则来看, base class 内所有名为 mf1 和 mf3 的函数都被 derived class 遮掩掉了。从类继承的观点来看, Base::mf1 和 Base::mf3 不再被 Derived class 继承。如你所见,即使 base class 和 derived class 内的函数有不同的参数类型,而且不论函数是 virtual 或者 non-virtual。
-
C++这些行为背后的基本理由是:
防止你在建立新的 derived class 时从 base class 继承其重载函数
。但不幸的是,如果你使用 public 继承而又不继承那些重载函数,就是在违反 is-a的关系,而这一点是被条例32重点强调的。因此当你使用 public 继承,你总是会推翻(override)C++默认遮掩继承名称的行为
。 -
一个避免遮掩名称的方式是:
使用 using 声明式
。让 base class 内的某些事物可以在 derived class 作用域中可见:class Derived : public Base { public: using Base::mf1; using Base::mf3; virtual void mf1(); void mf3(); };
现在可以放心使用 base class 中的函数而无需担心遮掩问题。注意使用 using 声明式的权限符为 public,注意不要违反继承时的继承权限。对于 public 继承,并不是所有的函数都被继承,因而不是所有的函数都可以进行声明访问。
尝试声明无法访问的函数,编译器会自动报错
。 -
但是 using 声明式并不能解决所有问题,因为 using 声明式会令继承而来的所有同名函数在 derived class 都可见,当我们需要
指定特定的同名函数时
,需要另外的技术:一个简单的转交函数(forwarding function)
:class Derived : public Base { public: virtual void mf1() { Base::mf1(); //base and inline } void mf3(); };
即:显式通过 base class 调用其同名函数。注意上面例子将 base 函数进行了 inline,可以为那些不支持 using 声明式的老旧编译器提供另一种扩大 derived class 作用域机会。
-
当继承结合 tamplates 时,我们又将面对继承名称被遮掩的一个全然不同的形式。我们将在条款43介绍。
二. 总结
- derived classes 内的名称会遮掩 base classes 内的名称。在 public 继承下从来没有人希望如此。
- 为了让被遮掩的名称再见天日,可使用 using 声明式或转交函数(forwarding functions)。