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

编程入门 行业动态 更新时间:2024-10-09 07:29:34

避免遮掩继承<a href=https://www.elefans.com/category/jswz/34/1766077.html style=而来的名称——条款33"/>

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

        这个题材和继承无关,而是和作用域有关,我们都知道在诸如这般的代码中:

int x;                // global变量
void someFunc()
{double x;        // local变量std::cin >> x;   // 读一个新值赋予local变量x
}

        这个读取数据的语句指涉的是local变量x,而不是global变量x,因为内层作用域的名称会遮掩外围作用域的名称。我们可以这样看本列的作用域形式:

                                          

        当编译器处于someFunc的作用域内并遭遇名称x时,它在local作用域内查找是否有什么东西带着这个名称。如果找到就不再找其他作用域。本例的someFunc的x是double类型而global x是int类型,但那不要紧。C++的名称遮掩规则所做的唯一事情就是:遮掩名称,至于名称是否应和相同或不同的类型,并不重要。本例中一个名为x的double遮掩了一个名为x的int。

        现在导入继承。我们知道,当位于一个derived class成员函数内指涉base class内的某物(也许是个成员函数、typedef、或成员变量)时,编译器可以找出我们所指涉的东西,因为derived class继承了声明于base classes内的所有东西。实际运作方式是,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三种,这是为了强调我们谈的是名称,和其他无关。本例使用单一继承,然而一旦了解单一继承下发生的事,很容易就可以推想C++在多继承下的行为。

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

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

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

        我们的目标并不是为了知道撰写编译器必须实践的名称查找规则,而是希望知道足够的信息,用以避免发生让人不快的惊讶。再次考虑前一个例子,这次让我们重载mf1和mf3,并且添加一个新版mf3到Derived去。如条款36所说,这里发生的事情是:Derived重载了mf3,那是一个继承而来的non-virtual函数。这会使整个设计立刻显得疑云重重,但为了充分认识继承体系内的“名称可视性”,我们暂时安之若素。

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();...
};

                                                               

        这段代码带来的行为会让每一位第一次面对它的C++程序员大吃一惊。以作用域为基础的“名称遮掩规则”并没有改变,因此base class内所有名为mf1和mf3的函数都被derived class内的mf1和mf3函数遮掩掉了。从名称查找观点来看Base::mf1和Base::mf3不再被Derived继承!

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

        如你所见,上述规则都适用,即使base classes和derived classes内的函数有不同的参数类型也适用,而且不论函数是virtual或non-virtual一体适用。这和本条款一开始展示的道理相同,当时函数someFunc内的double x遮掩了global作用域内的int x,如今Derived内的函数mf3遮掩了一个名为mf3单类型不同的Base函数。

        这些行为背后的基本理由是为了防止你在程序库或应用框架内建立新的derived class时附带地从疏远的base classes继承重载函数。不幸的是你通常会想继承重载函数。实际上如果你正在使用public继承而又不继承那些重载函数,就是违反base和derived classes之间的is-a关系,而条款32说过is-a是public继承的基石。因此你几乎总会想要推翻(override)C++对“继承而来的名称”的缺省遮掩行为。

        你可以使用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的所有using Base::mf3;        // 在Derived作用域都可见(并且public)virtual void mf1();void mf3();void mf4();...
};

        现在,继承机制将一如往昔地运作:

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

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

        有时候你并不想继承base classes的所有函数,这是可以理解的。在public继承下,这绝对不可能发生,因为它违反了public继承所暗示的“base和derived classes之间的is-a关系”。(这也就是为什么上述using声明式被放在derived class的public区域的原因:base class的public名称在publicly derived class内也应该是public。)然而在private继承之下(见条款39)它却可能是有意义的。例如假设Derived以private形式继承Base,而Derived唯一想继承的mf1是哪个无参数版本。using声明式在这里派不上用场,因为using声明式会另继承而来的某给定名称之所有同名函数在derived class中都可见。不,我们需要不同的技术,即一个简单的转交函数(forwarding function):

class Base {
private:int x;
public:virtual void mf1() = 0;virtual void mf1(int);...                      // 与前相同
};
class Derived: public Base {
public:virtual void mf1()    // 转交函数(forwarding function);{ Base::mf1(); }      // 暗自成为inline(见条款30)...
};
...
Derived d;
int x;
d.mf1();      // 很好,调用Derived::mf1
d.mf1(x);     // 错误!Base::mf1被遮掩了

        inline转交函数(forwarding function)的另一个用途是为那些不支持using声明式(注:这并非正确行为)的老旧编译器另辟一条新路,将继承而来的名称汇入derived class作用域内。

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

请记住

  • derived classes内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。
  • 为了让被遮掩的名称重见天日,可使用using声明式或转交函数(forwarding function)。

更多推荐

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

本文发布于:2024-02-14 01:08:36,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1761261.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:而来   条款   名称

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!