Effective C++ 条款36:绝不重新定义继承而来的 non-virtual 函数
文章目录
- 条款36:绝不重新定义继承而来的 non-virtual 函数
- 问题说明
- 示例:重新定义 `non-virtual` 函数
- 解析
- 为什么避免重新定义 `non-virtual` 函数
- 正确的设计方式
- 示例:使用 `virtual` 函数实现多态
- 总结
条款36:绝不重新定义继承而来的 non-virtual 函数
在 C++ 中,non-virtual
函数并不支持动态绑定。这意味着即使派生类重新定义了基类的 non-virtual
函数,调用函数时的行为取决于调用对象的静态类型,而非运行时的动态类型。
问题说明
当重新定义继承而来的 non-virtual
函数时,程序的行为可能会变得难以预测,尤其是通过基类指针或引用调用这些函数时,可能导致意外的结果。
示例:重新定义 non-virtual
函数
class Base { public: void nonVirtualFunc() const { std::cout << "Base::nonVirtualFunc" << std::endl; } }; class Derived : public Base { public: void nonVirtualFunc() const { // 不推荐,行为难以预测 std::cout << "Derived::nonVirtualFunc" << std::endl; } }; int main() { Base* b = new Derived; b->nonVirtualFunc(); // 输出: Base::nonVirtualFunc Derived d; d.nonVirtualFunc(); // 输出: Derived::nonVirtualFunc delete b; return 0; }
解析
-
基类指针或引用 调用
non-virtual
函数时,只会调用基类的实现,而不会调用派生类的重定义版本。- 在上例中,通过基类指针
b
调用nonVirtualFunc
,实际调用的是Base::nonVirtualFunc
,而非Derived::nonVirtualFunc
。
- 在上例中,通过基类指针
-
直接调用派生类对象的方法 则会调用派生类版本的函数。
- 在上例中,通过派生类对象
d
调用nonVirtualFunc
,调用的是Derived::nonVirtualFunc
。
- 在上例中,通过派生类对象
为什么避免重新定义 non-virtual
函数
- 意图混淆:重新定义
non-virtual
函数的代码容易让人误以为这些函数是多态的。 - 行为不一致:通过基类指针或引用调用时的行为与直接调用派生类对象的方法不同,可能导致潜在的错误。
- 设计问题:继承关系中的重定义通常表明代码设计存在问题。如果需要重定义,通常应该将函数声明为
virtual
。
正确的设计方式
如果需要多态行为,应该将函数声明为 virtual
;如果不需要多态行为,则应该避免重定义继承而来的 non-virtual
函数。
示例:使用 virtual
函数实现多态
class Base { public: virtual void virtualFunc() const { std::cout << "Base::virtualFunc" << std::endl; } }; class Derived : public Base { public: void virtualFunc() const override { std::cout << "Derived::virtualFunc" << std::endl; } }; int main() { Base* b = new Derived; b->virtualFunc(); // 输出: Derived::virtualFunc delete b; return 0; }
总结
- 不要重新定义继承而来的
non-virtual
函数,因为它不会实现多态行为。 - 如果需要多态行为,请使用
virtual
函数。 - 如果确实需要防止某些函数被派生类覆盖,可以将它们声明为
final
或非虚函数,并明确设计接口。
通过清晰区分 virtual
和 non-virtual
函数的用途,可以避免混乱和潜在的错误。