liangbm3's blog

Back

这是 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 声明可以将基类中所有同名的成员拉到派生类的作用域中,这是最常用且推荐的方法。
    class 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)
    cpp
    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),你可以使用转交函数。转交函数是派生类中定义的一个内联函数,它只是简单地调用它想继承的那个基类版本。
    class 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 的版本
        }
    };
    cpp
Effective C++: 避免遮掩继承而来的名称
https://liangbm3.site/blog/effective-c-bi-mian-zhe-yan-ji-cheng-er-lai-de-ming-cheng
Author liangbm3
Published at 2025年10月25日
Comment seems to stuck. Try to refresh?✨