Effective C++——条款33(第6章)

来源:互联网 发布:java代码换行符 编辑:程序博客网 时间:2024/06/04 18:39

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

Avoid hiding inherited names

     这个题材和作用域(scopes)有关,在诸如下面的代码:
int x;                  // global变量void someFunc() {    double x;           // local变量    std::cin >> x;      // 读取一个新值赋予local变量x}
    这段读取数据的语句涉及到的是local变量 x,而不是global变量 x,因为内层作用域的名称会遮掩外围作用域的名称.如果找不到就再找其他作用域.C++的名称遮掩规则所做的唯一事情就是:遮掩名称.
    当位于一个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();    ...};
    此例内含一组混合了 public 和 private 名称,以及一组成员变量和成员函数名称.这些成员函数包括pure virtual,impure virtual 和non-virtual 三种,这是为了强调谈的名称,和其他无关.整个讨论中唯一重要的是这些东西的名称,至于这些东西是什么并不重要.假设derived class 内的mf4的实现部分像这样:
void Derived::mf4() {    ...    mf2();    ...}
    当编译器看到这里使用名称mf2,必须估算他指涉什么东西,编译器的做法是查找各作用域,看看有没有某个名为mf2的声明式.首先查找local作用域(也就是mf4覆盖的作用域),在那里没找到任何东西,于是查找其外围作用域,也就是 class Derived覆盖的作用域.还是没找到任何名为mf2,于是再往外围移动,本例为base class.在那里编译器找到了一个名为mf2的东西,于是停止查找.如果base内还是没有mf2,查找动作继续下去,首先查找内含base的那个namespace的作用域,最后往global作用域去查找.
    考虑下面的代码:
class Base {private:    int x;public:    virtual void mf1() = 0;    virutal void mf1(int);    virtual void mf2();    void mf3();    void mf3(double);    ...};class Derived : public Base {public:    virtual void mf1();    void mf3();    void mf4();    ...};
    这段代码带来的行为会让每一个第一次面对它的C++程序员大吃一惊.以作用域为基础的"名称遮掩规则"并没有改变,因此base class 内所有名为mf1和mf3的函数都被derived class 内的mf1和mf3函数遮掩了.从名称查找观点看,Base::mf1和Base::mf3不再被Derived继承!
Derived d;int x;d.mf1();        // ok,调用Derived::mf1d.mf1(x);       // error!因为Derived::mf1遮掩了Base::mf1d.mf2();        // ok,调用Base::mf2d.mf3();        // ok,调用Derived::mf1d.mf3(x);       // error!因为Derived::mf3遮掩了Base::mf3
    上述规则都适用,即使base class 和derived class 内的函数有不同的参数类型也适用,而且不论函数是 virtual 或non-virtual 都适用.这和本条款一开始展示的道理相同,当时函数someFunc内的 double x 遮掩了global作用域内的 int x.现在Derived内的函数mf3遮掩了一个名称为mf3但类型不同的Base函数.
    这些行为背后的基本理由是为了防止在程序库或应用框架内建立新的derived class 时附带地从疏远的base class 继承重载函数.不幸的是通常会想继承重载函数.实际上如果正在使用 public 继承而又不继承那些重载函数,就违反base和derived class 之间的is-a关系,而条款32说过is-a是 public 继承的基石.
    可以使用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;        // 让Base class内名为mf1和mf3的所有东西在Derived作用域可见    using Base::mf3;    virtual void mf1();    void mf3();    void mf4();    ...};
    现在,继承机制将一如往昔地运作:
Derived d;int x;d.mf1();        // ok,调用Derived::mf1d.mf1(x);       // ok,调用Base::mf1d.mf2();        // ok,调用Base::mf2d.mf3();        // ok,调用Derived::mf3d.mf3(x);       // ok,调用Base::mf3
    这意味如果继承base class 并加上重载函数,而又希望重新定义或覆写其中一部分,那么必须为那些原本会被遮掩的每个名称引入一个 using 声明式,否则某些希望继承的名称会被这样.
    有时候并不想继承base class 的所有函数,这是可以理解的.在 public 继承下,这绝对不可能发生,因为它违反了 public 继承所暗示的"base和derived class之间的is-a关系"(这就是为什么上述 using 声明式被放在derived class 的 public 区域的原因:base class 内的 public 名称在publicly derived class 内也应该是 public).然而在 private 继承下(详见条款39)它却可能是有意义的.例如假设Derived以 private 形式继承Base,而Derived唯一想继承的mf1是那个无参版本. using 声明式这这里派不上用场,因为 using 声明式会令继承而来的某给定名称的所有同名函数在derived class 中都可见.需要不同的技术,即一个简单的转交函数:
class Base {public:    virtual void mf1() = 0;    virtual void mf1(int);    ...};class Derived : private Base {public:    virtual void mf1()      // 转交函数    { Base::mf1(); }        // 暗自称为inline    ...};Derived d;int x;d.mf1();        // ok,调用Base::mf1d.mf1(int);     // error!Base::mf1被遮掩
    inline 转交函数的另一个用途是为那些不支持 using 声明式的老旧编译器另辟一条新路,将继承而得到的名称汇入derived class 作用域内.
    注意:
    derived class 内的名称会遮掩base class 内的名称.在 public 继承下从来没有人希望如此.
    为了让被遮掩的名称出现,可适用 using 声明式或转交函数.

0 0
原创粉丝点击