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

本文探讨了C++中继承时的名称遮掩问题,如何通过using声明式和转交函数避免这种问题。在继承中,derived类的成员会遮掩base类的同名成员,导致可能的二义性和不期望的行为。using声明式可以使得base类的成员在derived类作用域内可见,而转交函数则允许显式调用base类的特定函数。这两种方法有助于保持代码的清晰和正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一. 内容

  1. 这个主题其实与作用域(Scope)有关。举个例子:

    int x=1;   //全局变量
    void func() {
       double x=1.1;  //局部变量
       std::cout<<x<<"\n";
    }
    

    当编译器在 func 的作用域并使用 x 时,它会先在 local 作用域查找是否存在该变量,如果找不到在扩大作用域。显然,编译器会在 local 找到 double x,然后停止查找,这意味着在 local 中使用 x,将总是找到 double x,而非 global 作用域的 int x。这便是我们所说的:遮掩名称。

  2. 谈到继承,继承的作用域机制也类似上面的例子,使用变量或者函数时,编译器会先
    查找 derived class 作用域,寻找失败再扩大范围到 base class 作用域。注意一点,编译器只会查找是否名称相同,而不在乎其变量类型或者参数类型只有当在同一作用域寻找到多个的情况下才考虑根据参数类型调用哪个函数比较合适,当存在多个合适的函数,就会抛出二义性的编译异常

  3. 为了充分认识到遮掩名称带来的危害,举个例子:

    class Base {
    public:
       virtual void mf1()=0;
       virtual void mf1(int);
       virtual void mf2();
       void mf3();
       void mf3(double);
    private:
    };
    
    class Derived : public Base {
    public:
       virtual void mf1();
       void mf3();
    };
    
    inline void Try() {
       Derived d;
       int x;
       d.mf1();
       d.mf1(x);  //报错,因为 derived:: mf1 遮掩了 base:: mf1 (int) 
       d.mf2();
       d.mf3();
       d.mf3(4); //报错,因为 derived::mf3 遮掩了 base::mf3 (double)
    }
    

    这段代码的行为会让每一位面对它的 C++ 程序员大吃一惊,以作用域为基础的名称遮掩规则来看, base class 内所有名为 mf1 和 mf3 的函数都被 derived class 遮掩掉了。从类继承的观点来看, Base::mf1 和 Base::mf3 不再被 Derived class 继承。如你所见,即使 base class 和 derived class 内的函数有不同的参数类型,而且不论函数是 virtual 或者 non-virtual。

  4. C++这些行为背后的基本理由是:防止你在建立新的 derived class 时从 base class 继承其重载函数。但不幸的是,如果你使用 public 继承而又不继承那些重载函数,就是在违反 is-a的关系,而这一点是被条例32重点强调的。因此当你使用 public 继承,你总是会 推翻(override)C++默认遮掩继承名称的行为

  5. 一个避免遮掩名称的方式是:使用 using 声明式。让 base class 内的某些事物可以在 derived class 作用域中可见:

    class Derived : public Base {
    public:
       using Base::mf1;
       using Base::mf3;
       virtual void mf1();
       void mf3();
    };
    

    现在可以放心使用 base class 中的函数而无需担心遮掩问题。注意使用 using 声明式的权限符为 public,注意不要违反继承时的继承权限。对于 public 继承,并不是所有的函数都被继承,因而不是所有的函数都可以进行声明访问。尝试声明无法访问的函数,编译器会自动报错

  6. 但是 using 声明式并不能解决所有问题,因为 using 声明式会令继承而来的所有同名函数在 derived class 都可见,当我们需要指定特定的同名函数时,需要另外的技术:一个简单的转交函数(forwarding function)

    class Derived : public Base {
    public:
       virtual void mf1() {
          Base::mf1();  //base and inline 
       }
       void mf3();
    };
    

    即:显式通过 base class 调用其同名函数。注意上面例子将 base 函数进行了 inline,可以为那些不支持 using 声明式的老旧编译器提供另一种扩大 derived class 作用域机会。

  7. 当继承结合 tamplates 时,我们又将面对继承名称被遮掩的一个全然不同的形式。我们将在条款43介绍。

二. 总结

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值