侧边栏壁纸
博主头像
如此肤浅

但行好事,莫问前程!

  • 累计撰写 52 篇文章
  • 累计创建 6 个标签
  • 累计收到 6 条评论

目 录CONTENT

文章目录

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

如此肤浅
2022-06-07 / 0 评论 / 0 点赞 / 25 阅读 / 2,417 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-06-07,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

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

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

这个读取数据的语句指涉的是 local 变量 x,而不是 global 变量 x,因为内层作用域的名称会遮掩外围作用域的名称。

在继承中

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

上述代码的实际运作方式是,derived class 作用域被嵌套在 base class 作用域内,如图所示:
image-1654567287038
此例内含一组混合了 public 和 private 名称,以及一组成员变量和成员函数名称。这些成员函数包括 pure virtual,、impure virtual 和 non-virtual 三种,这是为了强调我们谈的是名称,和其他无关。这个例子也可以加入各种名称类型,例如 enums、nestedclasses 和 typedefs。整个讨论中唯一重要的是这些东西的名称,至于这些东西是什么并不重要。本例使用单一继承,然而一旦了解单一继承下发生的事,很容易就可以推想 C++ 在多重继承下的行为。

假设 derived class 内的 mf4 的实现代码为:

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

当编译器看到 mf2 时,它的做法是查找各个作用域,看有没有某个名为 mf2 的声明式。首先查找 local 作用域(也就是 mf4 覆盖的作用域)。若没有找到再查找外围作用域,也就是 class Derived 覆盖的作用域。若还是没找到则再往外围移动,也就是 base class,在这里找到了一个名为 mf2 的东西就停止查找。如果在 Base 内还是没有 mf2,查找动作便继续下去,首先查找 Base 所在的那个 namesapce,最后往 global 作用域去找。

在知道了什么是名称遮掩规则后再来看下面这段代码:

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

image-1654568367649
由于名称遮掩规则,所以 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

若你想继承重载函数,可使用 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:
	// 让Base class内名为mf1和mf3的所有东西在Derived作用域内都可见
	using Base::mf1; 
    using Base::mf3;
	virtual void mf1();
    void mf3();
    void mf4();
    ...
};

image-1654568947947

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 classes 的所有函数,此时不能用 public 继承,因为“不想继承 base classes 的所有函数”违反了 public 继承的 is-a 关系,因此使用 private 继承更合适。假设 Derived 以 private 形式继承 Base,而 Derived 唯一想要继承的 mf1 是那个无参数的版本。因为 using 声明式会令继承而来的某给定名称的所有同名函数在 derived class 中都可见,所有 using 声明式在这里派不上用场。此时我们需要使用一个简单的转交函数:

class Base {
public:
	virtual void mf1() = 0;
    virtual void mf1(int);
    ... // 与前面相同
};

class Derived: private Base {
public:
	virtual void mf1() // 转交函数
    { Base::mf1(); }
    ...
};
Derived d;
int x;
d.mf1(); // 正确,调用的是Derived::mf1
d.mf1(x); // 错误!Base::mf1()被遮掩了

以上是有关继承和名称遮掩的所有内容!但是当继承结合 templates 时,“继承名称被遮掩”又会有新的形式,后面的条款会讨论。

0

评论区