effective C++ 条款 33:避免遮掩继承而来的名称

这个题材其实和继承无关,而是和作用域(scopes)有关。

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

这个指涉的是local变量x,而不是global变量x,因为内存作用域会的名称遮掩外围作用域的名称。

当编译器处于someFunc的作用域内并遭遇名称x时,他在local作用域内查找是否有什么东西带着这个名称。如果找到就不再找其他作用域。someFunc的x是double类型而global x是int类型,并不要紧。c++的名称遮掩规则(name-hiding rules)所做的唯一事情就是:遮掩名称。至于名称是否是相同或不同的类型,并不重要。

现在导入继承。当一个derived class成员函数内指涉(refer to)base class内的某物(也许是成员函数、typedef、或成员变量)时,编译器可以找出我们所指涉的东西,因为derived class继承声明于base class内的所有东西。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();
};

这个例子也可以加入各种名称类型,例如enums,nested classes和typedefs。整个讨论中唯一的重点是这些东西的名称,至于这些东西是什么并不重要。

假设derived class内的mf4的实现像这样:

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

当编译器看到这里使用名称mf2,必须估算它指涉什么东西。查找各作用域,看看有没有某个名为mf2声明式。首先查找local作用域(也就是mf4覆盖的作用域),没找到任何东西名为mf2.于是查找其外围作用域,class Derived覆盖的作用域。还是没找到任何东西名为mf2.于是再往外围移动,本例为base class。在那找到一个名为mf2的东西了,于是停止查找。如果Base还是没有mf2,查找便继续下去,首先找内含Base 的那个namespace的作用域,最后往global作用域找去。

这次我们重载mf1和mf3,并添加一个新版mf3到derived去。

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函数遮掩掉了。

Derived d;
int x;
d.mf1();
d.mf1(x);//错误,Derived::mf1遮掩了Base::mf1
d.mf2();
d.mf3();
d.mf3(x);//错误,Derived::mf3遮掩了Base::mf3

即使base class和derived classes内的函数有不同的参数类型也适用,而且不论函数是virtual或non-virtual也适用

这些行为背后的基本理由是为了防止你在程序库或应用框架内建立新的derived class时附带地从疏远的base class继承重载函数。

不幸的是,你通常会想继承重载函数。实际上你在使用public继承而不继承那些重载函数,就是违反base和derived classes之间的is-a关系,而is-a是public继承的基石。

你可以用using声明式达成目标:

class Derived : public Base {
public:

//base class内的public名称在publicly derived class内也应该是public。
    using Base::mf1;    // 让base class内为mf1和mf3的所有东西
    using Base::mf3;    //在Derived class作用域内都可见(并且public)
    virtual void mf1();
    void mf3();
    void mf4();
};

Derived d;
int x;
d.mf1();
d.mf1(x);//现在没问题了,调用Base::mf1
d.mf2();
d.mf3();
d.mf3(x);//现在没问题了,调用Base::mf3

这意味着如果你继承base class并加上重载函数,而你又希望重新定义或覆写(推翻)其中一部分,那么你必须为那些原本会被覆盖的每一个名称引入一个using声明式,否则某些你希望继承的名称会被覆盖。

有时候你并不希望继承base class的所有函数,但是在public继承下,这绝不可能发生。

然而在private继承之下(条款39)却可能是有意义的。如果Derived唯一想继承的mf1是那个无参数版本。using在这里派不上用场,using会令继承而来的某给定名称之所有同名函数在Derived class中都可见。我们需要一个简单的转交函数(forwarding function):

class Base {
private:
    int x;
public:
    virtual void mf1() = 0;
    virtual void mf1(int);
    virtual void mf2();
    void mf3();
    void mf3(double);
};

class Derived : private Base {
public:
    virtual void mf1()//转交函数
    {
        Base::mf1();//暗自转成inline
    }


};

Derived d;
int x;
d.mf1();//调用的是Derived::mf1
d.mf1(x);//错误,Base::mf1()被遮掩了

当继承结合templates,我们又将面对“继承名称被遮掩”的一个全然不同的形式。关于“以角括号定界”的所有东西,详见条款43.

原文链接: https://www.cnblogs.com/lidan/archive/2012/02/09/2344536.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    effective C++ 条款 33:避免遮掩继承而来的名称

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/41227

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月8日 下午6:06
下一篇 2023年2月8日 下午6:06

相关推荐