C++名字隐藏对公有继承的影响

来源:互联网 发布:淘宝账号买卖交易平台 编辑:程序博客网 时间:2024/06/08 14:48

在讨论名字隐藏对公有继承的影响前,让我们先来看看什么是名字隐藏,以及它在非继承结构中的影响。

C++中的名字隐藏(Name Hiding)规则简单地理解就是:当一个具有小作用域(inner scope)的对象A和一个作用域包含A(outer scope)的对象B同名时,在A的作用域中,B将不可见。也就是说B完全被A所屏蔽
  1. double i = 3.1415;  
  2. namespace XY  
  3. {  
  4.    bool i = false;  
  5.    void f()  
  6.    {  
  7.       int i = 926;  
  8.       cout << i << endl;  
  9.    }  
  10. }  
以上代码中i的输出值是926,因为全局double类型的i和名字空间XY中bool类型的i都被函数f中的同名局部变量所屏蔽。从这个例子中我们看到,名字隐藏和名字所属变量的类型完全无关。不同类型的变量,只要名字相同就会被屏蔽。

当名字隐藏发生的时候,某些情况下使用作用域运算符::是可以访问外层变量的。例如当你需要访问的外层变量处于全局作用域、名字空间、类中时。

  1. double i = 3.1415;  
  2. namespace XY  
  3. {  
  4.    bool i = false;  
  5.    void f()  
  6.    {  
  7.       int i = 926;  
  8.       // 输出0  
  9.       cout << XY::i << endl;  
  10.       // 输出3.1415  
  11.       cout << ::i << endl;  
  12.    }  
  13. }  
这个例子中,XY::i显式地告诉编译器说我要使用名字空间XY中的i,而::i则告诉编译器去使用全局空间的i。然而,在以下的嵌套作用域中是没有办法访问外层作用域变量的。
  1. void f()  
  2. {  
  3.    int i = 0;  
  4.    {  
  5.       double i = 3.1415;  
  6.       // 无法访问外层的整形变量i  
  7.       cout << i << endl;  
  8.    }  
  9. }  
那除了变量,函数是不是也有名字隐藏的问题呢?
  1. void f(int i)  
  2. {}  
  3.   
  4. void f(int i, int j)  
  5. {}  
  6.   
  7. namespace XY  
  8. {  
  9.    void f()  
  10.    {  
  11.       f(1); //编译出错,全局f(int i)已被屏蔽  
  12.       f(2, 3); //编译出错,全局f(int i, int j)已被屏蔽  
  13.    }  
  14. }  

从这个例子可以看出,只要函数名相同,所有外层的函数都会被屏蔽,即使类型不同。这点和我们前面在变量上观察到的现象完全一致。

那么,所有的这些会对继承有何影响呢?在一个继承体系中,派生类的作用域是内嵌在基类的作用域之中的。形象一点,就类似于下面的嵌套作用域。
  1. Base  
  2. //基类作用域开始  
  3.   ..  
  4.   Derived  
  5.   { //派生类作用域开始  
  6.     ..  
  7.       
  8.   } //派生类作用域结束  
  9.   
  10. //基类作用域结束  
让我们来看看这个例子。
  1. class Base  
  2. {  
  3. public:  
  4.    virtual void f();  
  5.    virtual void f(int i);  
  6.    virtual void f(int i, int j);  
  7. };  
  8.   
  9. class Derived: public Base  
  10. {  
  11. public:  
  12.    virtual void f();  
  13. };  
  14.   
  15. Derived d;  
  16. d.f();  
  17. d.f(1); //编译出错,基类f(int i)已被屏蔽  
  18. d.f(1, 2); //编译出错,基类f(int i, int j)已被屏蔽  
这个例子中,基类一共有三个重载的虚函数,都有相同的名字f。公有派生类重新定义(override, redefine)了基类中的一个函数——没有参数的那个f。我们知道,公有继承塑造了一个“Is-a”的关系,即任何的派生类对象都可以被当做基类对象来使用。这就要求:所有基类的公有接口派生类必须也同样拥有,否则“Is-a”的关系将被打破。本例中,因为无参数的f在派生类中被重新定义,导致其他两个同名函数在派生类中被屏蔽。所以,外界无法访问这两个接口。

这种情况并非不常见,因为函数的重载和虚函数的重定义都是C++常用特性。问题的解决方法是通过using语句把基类的同名函数的作用域扩展到派生类中来。当然,using的使用必须在派生类的public中,以确保这些接口依然是公有的。

  1. class Base  
  2. {  
  3. public:  
  4.    virtual void f();  
  5.    virtual void f(int i);  
  6.    virtual void f(int i, int j);  
  7. };  
  8.   
  9. class Derived: public Base  
  10. {  
  11. public:  
  12.    using Base::f;  
  13.    virtual void f();  
  14. };  
  15.   
  16. Derived d;  
  17. d.f();  
  18. d.f(1); //ok  
  19. d.f(1, 2); //ok  

小结:

  1. 尽量避免名字冲突。不要在全局空间使用类似i, j这样的变量名。
  2. 如果只重定义了基类几个重载函数中的一个,必须使用using语句保证公有派生类继承基类的所有同名接口。
  3. 另一个警醒是,如果你要在基类中重载一个现有函数时,千万要保证派生类中也有同样的接口!要么重定义,要么使用using。嗯。。在现有代码上增加代码并不是那么简单的。