c++: 为什么需要名字隐藏机制(c++ Why name hiding)?

写在前面
回答这个问题还是有难度的.这里我整理了stackoverflow和google论坛上的回答,作为对c++ hide的一个理解.

1 认识隐藏

c++中overload override 以及hide机制对于命名查找的理解很重要.一般来讲作用域查找是一个递归的过程,从内层作用域查找直至外层作用域,因此我们会认为,以下代码(来自: Why doesn’t overloading work for derived classes?):

#include<iostream>
using namespace std;

class B {
public:
        int f(int i) { cout << "f(int): "; return i+1; }
        // ...
};

class D : public B {
public:
        double f(double d) { cout << "f(double): "; return d+1.3; }
        // ...
};

int main()
{
        D* pd = new D;

        cout << pd->f(2) << '\n';
        cout << pd->f(2.3) << '\n';
}

的运行结果是:

f(int): 3
f(double): 3.6

实际上,运行结果为:

f(double): 3.3
f(double): 3.6

我们人为,f(int)应该更适合f(2)调用,因此在作用域中解析f时应该调用基类函数,但实际上当在子类看到f(double)时命名查找就停止了.重载对于跨作用域是无效的,这里D:f(double)隐藏了B::f(int)函数.
如果要在D中能查找到基类的函数,从而实现重载,则可以使用using指示符:

#include<iostream>
using namespace std;

class B {
public:
        int f(int i) { cout << "f(int): "; return i+1; }
        // ...
};

class D : public B {
public:
        double f(double d) { cout << "f(double): "; return d+1.3; }
        //use using to make f(int) visible
        using B::f; // make every f from B available
};

int main()
{
        D* pd = new D;

        cout << pd->f(2) << '\n';
        cout << pd->f(2.3) << '\n';
}

这次运行结果为:

f(int): 3
f(double): 3.6

对于虚函数,子类也会同样的隐藏基类函数,例如:

struct Base {
  virtual void foo(){}
  virtual void foo(int){}
  virtual void bar(){}
};

struct Derived: Base {
 virtual void foo(){}
};

int main() {
  Derived d;
  d.foo(1); // Error foo(int) is hidden
  d.bar();  // Fine calls Base::bar()
}

这里Derived::foo()隐藏了基类Base::foo(int)函数,因此编译器提示d.foo的可选函数为virtual void Derived::foo()接受0个参数,与foo(1)调用不符合,导致编译时错误.

2 为什么需要隐藏

google论坛上查到一个比较好理解的解释.这里对这个理解加以整理,给出如下.
例如:

#include <cassert>

struct Base {
  // ...
};

struct Derived: Base {
   bool foo(long) {return true;}
};

int main() {
  Derived d;
  assert(d.foo(1));
}

上述程序运行正常,子类调用foo(long)函数,一切Ok.
对于基类来讲,作者并不一定知道,子类的实现,假若作者添加了一个函数,代码如下:

#include <cassert>
// if name hiding not working, it may cause surprised behavior
// Since the Base author may not know the Derived code
// if the author of Base add a new method to Base
// then Derived may not be working as expected
struct Base {
  // ...
  bool foo(int) {return false;}
};

struct Derived: Base {
   using Base::foo; // simulate if name hiding not working
   bool foo(long) {return true;}
};

int main() {
  Derived d;
  assert(d.foo(1)); // will failed then print error info
}

假设没有命名隐藏机制,上述代码中我们使用using Base::foo来模拟没有命名隐藏机制,则上述代码运行时,由于调用基类Base::foo(int)而导致assert失效,出错.
也就是说:
假若没有隐藏机制,改变基类实现将有可能影响到子类已经正常工作的代码出现未预料的行为,这不是我们希望看到的.
因此,C++采用命名隐藏机制来避免引入这一麻烦.
当然,改变基类也并不一定会影响子类正常运行,这里只是举出一个特例加以说明.
相比如C++,我们看下java的实现:

public class  Base {
   public boolean foo(int x) {
     System.out.println("Base::foo called.");
     return false;
   }
}
// This example shows if the Base class add new method
// then Derived class may not working as expected

// compile:  javac *.java
// run : java -ea Derived

public class Derived extends Base {
  public boolean foo(long i) {
    System.out.println("Derived::foo called.");
    return true;
  }
  public static void main(String[] args) {
      Derived d = new Derived();
      assert(d.foo(1));
  }
}

上述代码,在基类没有引入foo(int)函数时,正常运行输出:

Derived::foo called.

加入基类引入了foo(int)函数,则程序assert发生错误,输出:

Base::foo called.
Exception in thread “main” java.lang.AssertionError
at Derived.main(Derived.java:14)

当基类重新引入函数foo(int)时,则assert判断出错,这不是我们想要的结果.

所以,C++引入命名隐藏也是有一定原因的,并不是有意加大语言学习的难度.

要了解更多,你可以访问:
[1] Why does an overridden function in the derived class hide other overloads of the base class?
[2] What is name hiding in C++?
[3] Behind the Scenes: Name Hiding in C++

  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值