在 C++ 中,名称查找的顺序受名称解析规则的控制,这些规则考虑了作用域、隐藏和继承的具体情况。关键点:
解决冲突的方法:
class Base { public: void foo(int) {} }; class Derived : public Base { public: using Base::foo; void foo(double) {} // 重载版本 };
Base::x 或 Base::foo()。在钻石继承中出现的歧义示例:
struct A { int x; }; struct B : virtual A {}; struct C : virtual A {}; struct D : B, C { void test() { x = 42; } // OK: x 只有一个,由于使用了 virtual };
如果在基类中声明了方法 foo(int),而在派生类中声明了 foo(double),是否可以从派生类对象中访问 foo(int)?
回答: 不可以,方法 foo(double) "隐藏" 了基类中的所有重载版本 foo。要访问 foo(int),必须明确写出 using Base::foo 或在调用中使用 Base::foo。
示例:
class Base { public: void foo(int) { } }; class Derived : public Base { public: void foo(double) {} }; Derived d; d.foo(3); // 错误!foo(int) 不可见
使用:
class Derived : public Base { public: using Base::foo; void foo(double) {} }; d.foo(3); // 一切正常
故事
在一个多重继承的项目中,出现了一个从两个基类继承的类,这两个类都有相同的方法 foo()。作者没有使用虚拟继承,导致创建了两个基类的副本,因此对 foo() 的所有调用变得模糊,编译器拒绝编译代码。通过使用虚拟继承和在调用时明确指定 Base::foo() 来解决。
故事
在一个图形引擎中,一个子类定义了自己的 draw(),没有为基类的 draw() 使用 using。在重构代码时调用了错误的 draw() 版本——某些接口部分未能渲染。错误只有在深入分析类结构后才发现。
故事
在迁移到新的编译器时,由于多重和虚拟继承导致的名称歧义,编译变得不可能。先前工作的实现没有考虑不同的 include 链接,造成了不同的名称集在翻译单元中。