莎士比亚对名字颇有感触。他问道:"名字算什么?玫瑰不叫玫瑰,依然芬芳如故。"这位诗人也写道:"窃取我名声之人......真使我变得贫穷。"言之有理。这引出了 C++ 中继承来的名称。
实际上,这个问题与继承无关,而是与作用域有关。我们都知道,在下面这段代码中:

读入 x 的语句引用的是局部变量 x,而非全局变量 x,因为内层作用域中的名称会隐藏("遮蔽")外层作用域中的名称。我们可以把作用域情况可视化如下:

当编译器处于 someFunc 的作用域中,遇到名称 x 时,会先在局部作用域中查找是否存在该名称。因为有,就不会再去检查其他作用域。在这个例子中,someFunc 的 x 类型是 double,而全局的 x 类型是 int,但这并不影响结果------C++ 的名称隐藏规则就是隐藏名称,无论这些名称对应的类型是否相同。在这个例子中,一个名为 x 的 double 隐藏了一个名为 x 的 int。
现在引入继承。我们知道,当我们在派生类成员函数内部引用基类中的某些东西(比如成员函数、typedef 或数据成员)时,编译器能找到我们所引用的对象,因为派生类继承了基类中声明的那些东西。其实际运作方式是:派生类的作用域嵌套在基类的作用域之内。例如:


在这个例子中,既有 public 和 private 名称,也有数据成员和成员函数的名称。成员函数包括纯虚函数、普通虚函数和非虚函数------这样安排是为了强调我们讨论的是名称。这个例子本来也可以包含类型名称,比如枚举、嵌套类和 typedef。在我们的讨论中,唯一重要的是它们是名称,至于这些名称具体是什么,则无关紧要。例子使用的是单继承,但一旦你理解了单继承下的运作机制,C++ 在多重继承下的行为也就不难推断了。
假设派生类中的 mf4 有一部分实现如下:

当编译器看到这里用到了名称 mf2 时,必须搞清楚它指的是什么。做法是在各作用域中搜索名为 mf2 的声明。首先查找局部作用域(即 mf4 的作用域),但没有找到任何名为 mf2 的声明。接着查找外围作用域,即 Derived 类的作用域,仍然没有。再往外查找基类的作用域,这次找到了名为 mf2 的东西,于是搜索停止。如果 Base 中也没有 mf2,搜索会继续,先进入包含 Base 的命名空间(如果有的话),最后到达全局作用域。
我刚才描述的查找过程是准确的,但并不是 C++ 中名称查找机制的完整全貌。不过,我们的目标并不是要懂到能写一个编译器的程度,而是足够了解以避免一些不必要的麻烦------就这个目的而言,我们已经掌握了足够的信息。
再回到前面的例子,但这次我们让 mf1 和 mf3 有重载版本,并且在 Derived 中也加一个 mf3 的版本(条款36指出,在派生类中重载继承来的非虚函数 mf3 会立刻让这个设计显得可疑,但为了理解继承下名称可见性的问题,我们先暂且忽略这一点):



这段代码的行为,会让每个C++程序员在第一次遇到时都感到惊讶。基于作用域的名称隐藏规则并没有改变------基类中所有名为 mf1 和 mf3 的函数,都被派生类中名为 mf1 和 mf3 的函数隐藏了。从名称查找的角度来看,Base::mf1 和 Base::mf3 不再被 Derived 继承!

如你所见,即使基类和派生类中的函数接受不同的参数类型,这一规则仍然适用;无论函数是虚函数还是非虚函数,也同样如此。就像本条款开头那样,函数 someFunc 中的 double x 隐藏了全局作用域中的 int x------此处 Derived 中的函数 mf3 也隐藏了基类中一个同名但类型不同的函数 mf3。
这种行为背后的设计初衷,是为了防止你在库或应用程序框架中创建新的派生类时,意外地从遥远的基类中继承了重载函数。然而,遗憾的是,你通常恰恰希望继承这些重载版本。事实上,如果你使用的是 public 继承,却没有继承这些重载函数,就违反了条款32所阐述的基类与派生类之间的 is-a 关系,而该关系正是 public 继承的基石。既然如此,你几乎总是需要覆盖 C++ 对继承名称的默认隐藏行为。
你可以通过 using 声明来实现这一点:


现在继承的行为就符合预期了:

这意味着,如果你从带有重载函数的基类继承,并且只打算重新定义或覆盖其中一部分,你就需要为每一个你本来会被隐藏的名称加上 using 声明。如果不这样做,一些你希望继承的名称就会被隐藏。
有时候,你也许并不想继承基类中的所有函数。在 public 继承下,这种情况本不该发生------因为那样做会再次违反 public 继承所要求的基类与派生类之间的 is-a 关系(这就是为什么上面的 using 声明放在了派生类的 public 区域:基类中为 public 的名称,在 public 派生的派生类中也应为 public)。但在 private 继承(参见条款39)下,这样做可能就有意义了。例如,假设 Derived 以 private 方式继承自 Base,而 Derived 只希望继承 mf1 中那个无参版本。这时 using 声明就派不上用场了,因为它会把所有同名函数都引入派生类的作用域。这种情况下,需要改用另一种技术:简单的转发函数。


内联转发函数的另一个用途,是应对那些古老编译器(它们的 using 声明不支持将继承名称导入派生类作用域)的情况。
以上就是关于继承与名称隐藏的全部内容了。但当继承与模板结合时,还会出现另一种形式的"继承名称被隐藏"问题。关于模板中涉及尖括号的那些细节,请参见条款43。
切记:
1.派生类中的名称会隐藏基类中的名称。在 public 继承下,这从来都不是我们想要的。
2.要让被隐藏的名称重新可见,可以使用 using 声明或转发函数。