Effective C++: 避免遮掩继承而来的名称
Avoid hiding inherited names
views
| comments
这是 Effective C++ 的第 33 个条款,Avoid hiding inherited names,中文翻译为:避免遮掩继承而来的名称。
在 C++ 中,名称遮蔽指的是在内层作用域中声明的名称,会遮掩外层作用域中的同名名称。例如:
int x = 10; // Global 作用域
void someFunc() {
double x = 5.0; // local 作用域
std::cin >> x; // 读取的是 local 变量 double x
// global 作用域的 int x 在这里被遮掩了
}cpp继承关系也可以看作一种作用域嵌套,Derived (派生类) 的作用域嵌套在 Base (基类) 的作用域中。继承中的名称遮蔽规则为当派生类定义了一个与其基类同名的成员(无论是成员变量还是成员函数)时,这个派生类成员会遮掩掉基类中所有同名的成员。注意:
- 遮掩所有同名成员: 只要名称相同,基类中所有同名成员(即使它们是不同参数列表的重载函数)都会被遮掩。
- 与参数和
virtual无关:遮掩规则只看名称,不看函数的参数列表、返回类型或是否为virtual。
文中举了一个具体的例子:
class Base {
public:
virtual void mf1() = 0;
virtual void mf1(int x); // mf1 被重载
void mf3();
void mf3(double x); // mf3 被重载
...
};
class Derived : public Base {
public:
virtual void mf1(); // 重写(override)了 Base::mf1()
void mf3(); // 定义了 Derived::mf3()
...
};
// --- 客户端代码 ---
Derived d;
int x = 10;
d.mf1(); // 没问题, 调用 Derived::mf1()
d.mf1(x); // 编译错误
d.mf3(); // 没问题, 调用 Derived::mf3()
d.mf3(x); // 编译错误 (x 会被隐式转换为 double)cpp在上面的例子中,Derived 类中的 mf1() 和 mf3() 成员函数分别遮掩了 Base 类中所有同名的重载版本。因此,尝试调用 d.mf1(x); 和 d.mf3(x); 会导致编译错误,因为编译器找不到匹配的函数。在 public 继承下,这几乎总是不是我们想要的行为。public 继承意味着 “is-a” (是一种) 关系,派生类理应继承基类的所有接口。名称遮掩破坏了这种关系。
对于这个问题,主要有两种解决方法:
- 使用
using声明。使用using声明可以将基类中所有同名的成员拉到派生类的作用域中,这是最常用且推荐的方法。
cppclass Derived : public Base { public: // 将 Base 中所有名为 mf1 的成员引入 Derived 的 public 作用域 using Base::mf1; // 将 Base 中所有名为 mf3 的成员引入 Derived 的 public 作用域 using Base::mf3; virtual void mf1(); // 依然重写 Base::mf1() void mf3(); // 重载(overload)了 Base::mf3() 和 Base::mf3(double) ... }; // --- 客户端代码 (现在可以正常工作) --- Derived d; int x = 10; d.mf1(); // 没问题, 调用 Derived::mf1() d.mf1(x); // 没问题,调用 Base::mf1(int) d.mf3(); // 没问题, 调用 Derived::mf3() d.mf3(x); // 没问题,调用 Base::mf3(double)using Base::mf1;告诉编译器,把Base中所有叫mf1的东西都看作Derived作用域的一部分。现在Derived的作用域中同时有了Base::mf1()、Base::mf1(int)和Derived::mf1()。根据C++的重载和重写规则:Derived::mf1()重写了Base::mf1()。Base::mf1(int)依然可见,成为Derived接口的一部分。mf3同理。 - 在某些特殊情况下,例如不想继承基类的所有重载版本(比如在
private继承中,using声明会使基类成员在派生类中变为public),你可以使用转交函数。转交函数是派生类中定义的一个内联函数,它只是简单地调用它想继承的那个基类版本。
cppclass Base { public: virtual void mf1(int x); ... }; // 假设我们只想暴露 mf1(int) 版本 class Derived : public Base { public: // 只重写无参数版本 virtual void mf1() { ... } // 用转交函数“继承” mf1(int) void mf1(int x) { Base::mf1(x); // 显式调用 Base 的版本 } };