当前位置: 首页 > news >正文

Effective C++ 条款36: 绝不重新定义继承而来的非虚函数

Effective C++ 条款36:绝不重新定义继承而来的非虚函数


核心思想非虚函数在继承体系中代表不变性(invariant),重新定义继承的非虚函数会破坏"is-a"关系,导致对象行为不一致、多态失效和代码脆弱性。这是C++中绝对禁止的设计错误。

⚠️ 1. 非虚函数的本质与风险

设计意义

  • 非虚函数表示不变行为:所有派生类必须严格遵循的实现
  • 体现类不变式(class invariant):对象生命周期内必须保持的特性
  • 保证行为一致性:无论通过基类还是派生类接口调用,行为必须相同

违反后果

class Base {
public:void log() const { // 非虚函数std::cout << "Base log entry\n"; }
};class Derived : public Base {
public:void log() const { // 错误:重新定义非虚函数!std::cout << "Derived log entry\n";}
};// 使用场景
Derived d;
d.log();           // 输出 "Derived log entry"Base* pb = &d;
pb->log();         // 输出 "Base log entry"!行为不一致Base& rb = d;
rb.log();          // 输出 "Base log entry"!静态绑定

问题分析

  1. 破坏多态:通过基类指针/引用调用时,总是调用基类版本
  2. 违反LSP:派生类对象无法替换基类对象(行为改变)
  3. 代码脆弱
    • 函数隐藏:Derived::log隐藏了Base::log而非覆盖
    • 调用困惑:相同对象不同接口表现不同

🚨 2. 错误模式与正确实践

常见错误场景

class Account {
public:void withdraw(double amount) { // 非虚函数if (amount > balance_) throw InsufficientFunds();balance_ -= amount;}
};class CheckingAccount : public Account {
public:void withdraw(double amount) { // 错误重定义// 添加手续费逻辑Account::withdraw(amount + fee_); }
};CheckingAccount acc;
acc.withdraw(100);   // ✅ 正确扣除手续费Account* pa = &acc;
pa->withdraw(100);   // ❌ 未扣除手续费!资金错误

解决方案1:改为虚函数(需保证派生类行为兼容)

class Account {
public:virtual void withdraw(double amount) {// 基础验证逻辑if (amount > balance_) throw InsufficientFunds();balance_ -= amount;}
};class CheckingAccount : public Account {
public:void withdraw(double amount) override {// 添加额外逻辑Account::withdraw(amount + fee_); }
};

解决方案2:非虚接口模式(NVI)

class Account {
public:void withdraw(double amount) { // 非虚公开接口// 通用前置条件检查validate(amount); doWithdraw(amount);postTransaction();}
private:virtual void doWithdraw(double amount) = 0; // 实现可定制
};class CheckingAccount : public Account {
private:void doWithdraw(double amount) override {// 实现手续费逻辑actualWithdraw(amount + fee_);}
};

⚖️ 3. 设计决策指南
场景正确做法错误做法
通用不变行为✅ 基类非虚函数⛔ 派生类重定义
可定制的核心行为✅ 虚函数(public或private)⛔ 重定义非虚函数
添加派生类特有功能✅ 新命名函数⛔ 重定义基类非虚函数
强化前置条件/弱化后置条件⚠️ 避免(违反LSP)⛔ 绝对禁止

现代C++防护机制

class Base {
public:// C++11:显式禁止覆盖void stableAPI() final; // 非虚函数也可用final// C++20:契约强化void criticalOperation(int x) [[expects: x > 0]] [[ensures: resourceAllocated()]]{// 实现}
};class Derived : public Base {
public:void stableAPI(); // 错误:final函数不可重定义void criticalOperation(int x) { // 错误:无法弱化前置条件// 必须满足 x>0 且确保资源分配}
};

💡 关键设计原则

  1. 静态绑定与动态绑定

    • 非虚函数:静态绑定(编译期根据声明类型确定)
    • 虚函数:动态绑定(运行期根据对象类型确定)
    Derived d;
    Base* pb = &d;pb->nonVirtual(); // 调用Base::nonVirtual(静态)
    pb->virtualFunc(); // 调用Derived::virtualFunc(动态)
    
  2. 名称隐藏规则

    class Base {
    public:void func(int);
    };class Derived : public Base {
    public:void func(double); // 隐藏Base::func(int)!
    };Derived d;
    d.func(42); // 调用Derived::func(double)// 需要static_cast<Base>(d).func(42)访问基类版本
    
  3. 模板方法模式应用

    class Document {
    public:void save() const { // 非虚模板方法checkPermissions();doSave();      // 虚函数钩子updateLog();}
    private:virtual void doSave() const = 0; // 派生类实现
    };class PdfDocument : public Document {
    private:void doSave() const override { // 实现定制保存// PDF专用保存逻辑}
    };// 所有文档类型统一调用接口
    PdfDocument doc;
    doc.save(); // 正确执行完整流程
    

危险模式重现

class Rectangle {
public:void setSize(int w, int h) {width_ = w;height_ = h;}
};class Square : public Rectangle {
public:void setSize(int s) { // 错误:重定义非虚函数width_ = height_ = s;}// 隐藏Rectangle::setSize(int,int)!
};Square s;
s.setSize(10);      // ✅ 调用Square版本
s.Rectangle::setSize(10, 20); // ✅ 显式调用(但破坏正方形不变式)Rectangle& r = s;
r.setSize(10, 20); // ❌ 编译通过,但破坏正方形不变式

安全重构方案

class Shape {
public:virtual void setSize(int w, int h) = 0;
};class Rectangle : public Shape {
public:void setSize(int w, int h) override { ... }
};class Square : public Shape {
public:void setSize(int w, int h) override {if (w != h) throw NotSquareException();size_ = w;}void setSize(int s) { // 重载(非重定义)size_ = s;}
};

C++17编译时检查

template<typename Derived>
class NonVirtualEnforcer {
public:~NonVirtualEnforcer() {static_assert(!std::is_same_v<decltype(&Derived::criticalFunc), decltype(&NonVirtualEnforcer::criticalFunc)>,"Do not redefine non-virtual functions!");}void criticalFunc();
};class Base : public NonVirtualEnforcer<Base> {};class Derived : public Base {
public:void criticalFunc(); // 触发static_assert错误
};
http://www.dtcms.com/a/326582.html

相关文章:

  • 数据结构:树与二叉树
  • ARM基础概念 day51
  • easyExcel嵌套子集合导出Excel
  • 2025第十六届蓝桥杯大赛青少组省赛C++真题(初级组和中级组)
  • MCU的设计原理
  • SNMP入门教程:Windows下编译
  • Linux811 YUM;SHELL:if else fi,for
  • 进程线程切换的区别
  • 【k近邻】 K-Nearest Neighbors算法k值的选择
  • 第4节 大模型推理内存与计算优化
  • 【FreeRTOS】任务间通讯6: 任务通知- Task Notification
  • python+requests+yaml实现接口自动化用例
  • 【软件分享】格式工厂FormatFactory便携版本(解压即用)
  • 介绍一下jQuery的AJAX异步请求
  • Salesforce案例:零售企业会员积分体系
  • 新人如何简化学习Vue3文件
  • LangChain框架之 invoke() 方法
  • 【每日一错】PDB之间数据迁移
  • 7.Java的继承
  • 分享一款基于STC8H8K32U-45I-LQFP48单片机的4路数字量输入输出模块
  • 多重时间聚合算法(MAPA)改进需求预测模型
  • Redis实现排行榜
  • C++11 auto关键字:智能类型推导指南
  • 字符串相关例题(查询子串在主串中的个数)
  • GB17761-2024标准与电动自行车防火安全的技术革新
  • 编译GCC-12.1.0
  • Linux内核网络设备框架及其注册流程分析
  • Cursor 实用技巧与常见难题解析:从入门到进阶的技术实践
  • Pytest项目_day11(fixture、conftest)
  • OSPF IP FRR 理论和实验