zoukankan      html  css  js  c++  java
  • 读书笔记_Effective_C++_条款三十三:避免遮掩继承而来的名称

    名称的遮掩可以分成变量的遮掩与函数的遮掩两类,本质都是名字的查找方式导致的,当编译器要去查找一个名字时,它一旦找到一个相符的名字,就不会再往下去找了,因此遮掩本质上是优先查找哪个名字的问题。

    而查找是分作用域的,虽然本条款的命名是打着“继承”的旗子来说的,但我觉得其实与继承并不是很有关系,关键是作用域。

    举例子说明这个问题会比较好理解。

    1 //例1:普通变量遮掩
    2 int i = 3;
    3 
    4 int main()
    5 {
    6     int i = 4;
    7     cout << i << endl; // 输出4
    8 }

    这是一个局部变量遮掩全局变量的例子,编译器在查找名字时,优先查找的是局部变量名,找到了就不会再找,所以不会有warning,不会有error,只会是这个结果。

     1 //例2:成员变量遮掩
     2 class Base
     3 {
     4 public:
     5     int x;
     6     Base(int _x):x(_x){}
     7 };
     8 
     9 class Derived: public Base
    10 {
    11 public:
    12     int x;
    13     Derived(int _x):Base(_x),x(_x + 1){}
    14 };
    15 
    16 int main()
    17 {
    18     Derived d(3);
    19     cout << d.x << endl; //输出4
    20 }

    因为定义的是子类的对象,所以会优先查找子类独有的作用域,这里已经找到了x,所以不会再查找父类的作用域,因此输出的是4,如果子类里没有另行声明x成员变量,那么才会去查找父类的作用域。那么这种情况下如果想要访问父类的x,怎么办呢?

    可以在子类里面添加一个方法:

    int GetBaseX() {return Base::x;}

    利用Base::x,可以使查找指定为父类的作用域,这样就能返回父类的x的值了。

     1 //例3:函数的遮掩
     2 class Base
     3 {
     4 public:
     5     void CommonFunction(){cout << "Base::CommonFunction()" << endl;}
     6     void virtual VirtualFunction(){cout << "Base::VirturalFunction()" << endl;}
     7     void virtual PureVirtualFunction() = 0;
     8 };
     9 
    10 class Derived: public Base
    11 {
    12 public:
    13     void CommonFunction(){cout << "Derived::CommonFunction()" << endl;}
    14     void virtual VirtualFunction(){cout << "Derived::VirturalFunction()" << endl;}
    15     void virtual PureVirtualFunction(){cout << "Derived::PureVirtualFunction()" << endl;}
    16 };
    17 
    18 int main()
    19 {
    20     Derived d;
    21     d.CommonFunction(); // Derived::CommonFunction()
    22     d.VirtualFunction(); // Derived::VirtualFunction()
    23     d.PureVirtualFunction(); // Derived::PureVirtualFunction()
    24     return 0;
    25 }

    与变量遮掩类似,函数名的查找也是先从子类独有的作用域开始查找的,一旦找到,就不再继续找下去了。这里无论是普通函数,虚函数,还是纯虚函数,结果都是输出子类的函数调用。

    下面我们来一个难点的例子。

     1 //例4:重载函数的遮掩
     2 class Base
     3 {
     4 public:
     5     void CommonFunction(){cout << "Base::CommonFunction()" << endl;}
     6     void virtual VirtualFunction(){cout << "Base::VirturalFunction()" << endl;}
     7     void virtual VirtualFunction(int x){cout << "Base::VirtualFunction() With Parms" << endl;}
     8     void virtual PureVirtualFunction() = 0;
     9 };
    10 
    11 class Derived: public Base
    12 {
    13 public:
    14     void CommonFunction(){cout << "Derived::CommonFunction()" << endl;}
    15     void virtual VirtualFunction(){cout << "Derived::VirturalFunction()" << endl;}
    16     void virtual PureVirtualFunction(){cout << "Derived::PureVirtualFunction()" << endl;}
    17 };
    18 
    19 int main()
    20 {
    21     Derived d;
    22     d.VirtualFunction(3); // ?
    23     return 0;
    24 }

    很多人都会认为输出的是Base::VirtualFunction() With Parms,实际上这段代码却是编译不过的。因为编译器在查找名字时,并没有“为重载而走的很远”,C++的确是支持重载的,编译器在发现函数重载时,会去寻找相同函数名中最为匹配的一个函数(从形参个数,形参类型两个方面考虑,与返回值没有关系),如果大家的匹配程度都差不多,那么编译器会报歧义的错。

    但以上法则成立的条件是这些函数位于相同的作用域中,而这里是不同的域!编译器先查找子类独有的域,一旦发现了完全相同的函数名,它就已经不再往父类中找了!在核查函数参数时,发现了没有带整型形参,所以直接报编译错了。

    如果去掉子类的VirualFunction(),那么才会找到父类的VirtualFunction(int)。

    提醒一下,千万不要被前面的Virtual关键字所误导,你可以试一个普通函数,结果是一样的,只要子类中有同名函数,不管形参是什么,编译器都不会再往父类作用域里面找了。

    好,如果现在你非要访问父类里面的方法,那也可以,书上给出了两种作法,一种是采用using声明,另一种是定义转交函数。

     1 //例5:采用using声明,使查找范围扩大至父类指定的函数:
     2 class Base
     3 {
     4 public:
     5     void CommonFunction(){cout << "Base::CommonFunction()" << endl;}
     6     void virtual VirtualFunction(){cout << "Base::VirturalFunction()" << endl;}
     7     void virtual VirtualFunction(int x){cout << "Base::VirtualFunction() With Parms" << endl;}
     8     void virtual PureVirtualFunction() = 0;
     9 };
    10 
    11 class Derived: public Base
    12 {
    13 public:
    14     using Base::VirtualFunction; // 第一级查找也要包括Base::VirtualFunction
    15     void CommonFunction(){cout << "Derived::CommonFunction()" << endl;}
    16     void virtual VirtualFunction(){cout << "Derived::VirturalFunction()" << endl;}
    17     void virtual PureVirtualFunction(){cout << "Derived::PureVirtualFunction()" << endl;}
    18 };
    19 
    20 int main()
    21 {
    22     Derived d;
    23     d.VirtualFunction(3); // 这样没问题了,编译器会把父类作用域里面的函数名VirtualFunciton也纳入第一批查找范围,这样就能发现其实是父类的函数与main中的调用匹配得更好(因为有一个形参),这样会输出Base::VirtualFunction() With Parms
    24     return 0;
    25 }

    用了using,实际上是告诉编译器,把父类的那个函数也纳入第一批查找范围里面,这样就能发现匹配得更好的重载函数了。

     1 //例6:使用转交函数强制指定父类的作用域
     2 class Base
     3 {
     4 public:
     5     void CommonFunction(){cout << "Base::CommonFunction()" << endl;}
     6     void virtual VirtualFunction(){cout << "Base::VirturalFunction()" << endl;}
     7     void virtual VirtualFunction(int x){cout << "Base::VirtualFunction() With Parms" << endl;}
     8     void virtual PureVirtualFunction() = 0;
     9 };
    10 
    11 class Derived: public Base
    12 {
    13 public:
    14     using Base::VirtualFunction;
    15     void CommonFunction(){cout << "Derived::CommonFunction()" << endl;}
    16     void virtual VirtualFunction(){cout << "Derived::VirturalFunction()" << endl;}
    17     void virtual PureVirtualFunction(int x){cout << "Derived::PureVirtualFunction()" << endl;}
    18     void virtual VirtualFunction(int x){Base::VirtualFunction(x)};
    19 };
    20 
    21 int main()
    22 {
    23     Derived d;
    24     d.VirtualFunction(3); // 输出Base::VirtualFunction() With Parms
    25     return 0;
    26 }

    采用这种做法,需要在子类中再定义一个带int参的同名函数,在这个函数里面用Base进行作用域的指定,从而调用到父类的同名函数。

    快结尾了,这里还是声明一下,在不同作用域内定义相同的名字,无论是发生在变量还是函数身上,都是非常无聊也是不好的做法。除了考试试卷上,还是不要把名字遮掩问题带到任何地方,命个不同的名字真的有那么难吗?

    最后总结一下:

    1. derived classses内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。

    2. 为了让被遮掩的名称再见天日,可使用using声明式或转交函数(forwarding functions)。

  • 相关阅读:
    关于 相对论 的 一些 讨论推理 杂集
    反相 大全
    收录 猴哥 对于 相对论 水星进动 星光偏折 引力透镜 GPS 的 说法
    哲学 一词 起源于 古希腊 的 “爱智慧”
    字符流的父类
    字符编码
    对象流
    BufferStream 缓存流
    OutputStream 以及 使用文件输入输出流实现文件的复制操作
    InputStream
  • 原文地址:https://www.cnblogs.com/jerry19880126/p/3561413.html
Copyright © 2011-2022 走看看