Effective C 条款34区分接口继承和实现继承public 继承看似简单实则包含两个可分离的部分接口继承与实现继承。理解它们的区别是设计出优雅继承体系的关键一步。一、问题的提出继承的两种含义在 C 中当我们写下这样的代码时到底在继承什么classBase{public:virtualvoidfunc1()0;// pure virtualvirtualvoidfunc2();// impure virtualvoidfunc3();// non-virtual};classDerived:publicBase{// Derived 从 Base 继承了什么};表面上看Derived继承了Base的三个函数。但深入思考会发现func1没有实现派生类必须自己实现func2有默认实现派生类可以覆盖也可以不覆盖func3有固定实现派生类不应该覆盖这三种函数代表了三种截然不同的继承语义。混淆它们是继承设计中常见的错误。二、三种成员函数的继承语义2.1 Pure Virtual 函数只继承接口classShape{public:virtual~Shape()default;// Pure virtual 函数只指定接口不指定实现virtualvoiddraw()const0;virtualdoublearea()const0;virtualdoubleperimeter()const0;};classCircle:publicShape{public:Circle(doubleradius):radius_(radius){}// 必须实现所有 pure virtual 函数voiddraw()constoverride{std::cout绘制圆形半径radius_\n;}doublearea()constoverride{return3.14159*radius_*radius_;}doubleperimeter()constoverride{return2*3.14159*radius_;}private:doubleradius_;};classRectangle:publicShape{public:Rectangle(doublew,doubleh):width_(w),height_(h){}voiddraw()constoverride{std::cout绘制矩形width_xheight_\n;}doublearea()constoverride{returnwidth_*height_;}doubleperimeter()constoverride{return2*(width_height_);}private:doublewidth_,height_;};Pure Virtual 函数的设计意图特性说明接口契约定义派生类必须支持的接口强制实现派生类必须提供自己的实现否则无法实例化抽象基类包含 pure virtual 的类是抽象类不能创建对象多态基础为运行时多态提供统一的调用接口令人意外的事实C 允许为 pure virtual 函数提供定义classShape{public:// 声明为 pure virtual但仍可提供默认实现virtualvoiddraw()const0;virtualdoublearea()const0;};// 为 pure virtual 函数提供定义voidShape::draw()const{std::cout默认绘制实现\n;}doubleShape::area()const{return0.0;// 默认面积}classTriangle:publicShape{public:// 可以选择调用基类的默认实现voiddraw()constoverride{Shape::draw();// 调用 pure virtual 的默认实现std::cout三角形自定义绘制\n;}doublearea()constoverride{// 必须提供自己的计算returnbase_*height_/2;}private:doublebase_,height_;};这种有定义的 pure virtual 函数的用途为派生类提供一个可选的默认实现但强制派生类显式决定是否使用它。2.2 Impure Virtual 函数继承接口和默认实现classAnimal{public:virtual~Animal()default;// Impure virtual继承接口 默认实现virtualvoidmakeSound()const{std::cout某种动物叫声\n;// 默认实现}virtualvoidmove()const{std::cout动物在移动\n;// 默认实现}};classDog:publicAnimal{public:// 覆盖默认实现提供特定行为voidmakeSound()constoverride{std::cout汪汪\n;}// move() 继承默认实现不需要覆盖};classCat:publicAnimal{public:voidmakeSound()constoverride{std::cout喵喵~\n;}};classFish:publicAnimal{public:voidmakeSound()constoverride{std::cout...鱼不会叫\n;}voidmove()constoverride{std::cout鱼在游泳\n;}};Impure Virtual 函数的设计意图特性说明接口 默认实现派生类继承函数的签名和默认行为可选覆盖派生类可以选择使用默认实现或提供自己的实现代码复用为大多数派生类提供通用实现减少重复代码2.3 Non-Virtual 函数继承接口和强制实现classBase{public:// Non-virtual 函数接口 强制实现voidalgorithm(){// 算法的框架不允许派生类改变step1();step2();step3();}virtual~Base()default;protected:// 这些步骤派生类可以定制virtualvoidstep1()0;virtualvoidstep2()0;virtualvoidstep3()0;};// 更典型的例子classClock{public:// 获取当前时间所有时钟的统一接口不允许覆盖TimegetCurrentTime()const{returnreadHardwareClock();}// 格式化显示统一的显示方式std::stringformatTime(constTimet)const{returnt.toString(YYYY-MM-DD HH:MM:SS);}private:virtualTimereadHardwareClock()const0;// 硬件相关由派生类实现};classSystemClock:publicClock{private:TimereadHardwareClock()constoverride{// 读取系统时钟returnTime::now();}};classAtomicClock:publicClock{private:TimereadHardwareClock()constoverride{// 读取原子钟returnfetchAtomicTime();}};Non-Virtual 函数的设计意图特性说明不变性表示派生类不应该改变的行为一致性确保所有派生类的某个行为完全一致静态绑定调用在编译期确定性能更好三、三种函数的对比总结classBase{public:// 1. Pure Virtual只继承接口virtualvoidinterfaceOnly()0;// 2. Impure Virtual继承接口 默认实现virtualvoidinterfaceWithDefault(){std::cout默认实现\n;}// 3. Non-Virtual继承接口 强制实现voidinterfaceWithMandatory(){std::cout这是唯一实现不允许覆盖\n;}};函数类型继承接口继承实现派生类能否覆盖设计意图Pure Virtual是否可选项必须覆盖“你必须实现这个功能”Impure Virtual是是默认可选覆盖“你可以使用默认实现也可以自定义”Non-Virtual是是强制不应该覆盖“所有派生类的这个行为必须一致”四、一个完整的实际案例游戏 AI 系统让我们用一个游戏 AI 系统来展示三种函数的正确使用#includeiostream#includestring#includevector#includememory// AI 行为基类定义所有 AI 的通用接口classAIBehavior{public:virtual~AIBehavior()default;// Pure Virtual每个 AI 必须有自己的实现 // 评估当前状态并决定下一步行动virtualvoidevaluate()0;// 执行选定的行动virtualvoidexecute()0;// 获取 AI 的名称virtualstd::stringgetName()const0;// Impure Virtual有默认实现但可覆盖 // 更新 AI 状态每帧调用// 默认实现先评估再执行virtualvoidupdate(floatdeltaTime){evaluate();if(hasActionSelected()){execute();}}// 受到伤害时的反应// 默认实现简单的退缩virtualvoidonDamageTaken(intdamage){std::coutgetName() 受到 damage 点伤害\n;health_-damage;if(health_0){onDeath();}}// Non-Virtual所有 AI 的共同行为不允许改变 // 获取当前生命值所有 AI 的生命值计算方式相同intgetHealth()const{returnhealth_;}// 检查 AI 是否存活统一的判断逻辑boolisAlive()const{returnhealth_0;}// 注册到 AI 管理器统一的注册流程voidregisterToManager(AIManagermanager){manager.registerAI(this);onRegistered();}protected:inthealth_100;boolhasAction_false;boolhasActionSelected()const{returnhasAction_;}// 派生类可以覆盖的钩子virtualvoidonDeath(){std::coutgetName() 死亡。\n;}virtualvoidonRegistered(){}};// 具体的 AI 实现巡逻的守卫classPatrolGuardAI:publicAIBehavior{public:std::stringgetName()constoverride{return巡逻守卫;}voidevaluate()override{// 检查视野内是否有敌人if(detectEnemy()){selectedAction_Action::Attack;hasAction_true;}elseif(shouldPatrol()){selectedAction_Action::Patrol;hasAction_true;}else{hasAction_false;}}voidexecute()override{switch(selectedAction_){caseAction::Attack:std::cout守卫发现敌人发起攻击\n;break;caseAction::Patrol:std::cout守卫继续巡逻...\n;break;}}// 覆盖默认的伤害反应守卫会呼叫支援voidonDamageTaken(intdamage)override{AIBehavior::onDamageTaken(damage);// 先调用默认处理if(isAlive()){std::cout守卫呼叫支援\n;callForBackup();}}private:enumclassAction{Attack,Patrol};Action selectedAction_;booldetectEnemy(){/* ... */returnfalse;}boolshouldPatrol(){/* ... */returntrue;}voidcallForBackup(){/* ... */}};// 具体的 AI 实现Boss 怪物classBossAI:publicAIBehavior{public:std::stringgetName()constoverride{returnBoss;}voidevaluate()override{// Boss 有更复杂的决策逻辑if(health_30){selectedAction_Action::Enrage;hasAction_true;}elseif(canUseSpecialAttack()){selectedAction_Action::SpecialAttack;hasAction_true;}else{selectedAction_Action::NormalAttack;hasAction_true;}}voidexecute()override{switch(selectedAction_){caseAction::Enrage:std::coutBoss 进入狂暴状态\n;attackPower_*2;break;caseAction::SpecialAttack:std::coutBoss 释放必杀技\n;break;caseAction::NormalAttack:std::coutBoss 普通攻击。\n;break;}}// Boss 覆盖默认更新狂暴时更新频率更高voidupdate(floatdeltaTime)override{if(isEnraged_){// 狂暴时更新两次AIBehavior::update(deltaTime);AIBehavior::update(deltaTime);}else{AIBehavior::update(deltaTime);}}private:enumclassAction{Enrage,SpecialAttack,NormalAttack};Action selectedAction_;intattackPower_50;boolisEnraged_false;boolcanUseSpecialAttack(){/* ... */returnfalse;}};// 使用示例classAIManager{public:voidregisterAI(AIBehavior*ai){aiList_.push_back(ai);}voidupdateAll(floatdeltaTime){for(auto*ai:aiList_){if(ai-isAlive()){// Non-virtual统一的存活检查ai-update(deltaTime);// Virtual调用各自的更新逻辑}}}private:std::vectorAIBehavior*aiList_;};设计分析函数类型设计理由evaluate()/execute()/getName()Pure Virtual每种 AI 的行为都不同必须各自实现update()/onDamageTaken()Impure Virtual大多数 AI 使用默认逻辑但特殊 AI如 Boss可以覆盖getHealth()/isAlive()/registerToManager()Non-Virtual所有 AI 的这些行为应该一致不允许改变五、常见陷阱默认实现的危险Impure virtual 函数虽然方便但也存在隐患classAirplane{public:virtualvoidfly(){// 默认实现普通飞机的飞行方式std::cout使用默认飞行算法\n;}};classBoeing747:publicAirplane{// 使用默认的 fly() 实现——合理};classModelC172:publicAirplane{// 使用默认的 fly() 实现——合理};// 危险新增一种飞机忘记覆盖 fly()classSpaceShuttle:publicAirplane{// 糟糕航天飞机不应该使用普通飞机的飞行算法// 但编译器不会报错因为 fly() 有默认实现};解决方案将接口和默认实现分离classAirplane{public:// 纯虚函数只声明接口virtualvoidfly()0;protected:// 默认实现单独提供派生类必须显式选择是否使用voiddefaultFly(){std::cout使用默认飞行算法\n;}};classBoeing747:publicAirplane{public:voidfly()override{defaultFly();// 显式选择使用默认实现}};classSpaceShuttle:publicAirplane{public:voidfly()override{// 必须自己实现无法意外使用默认版本std::cout使用航天飞机专用飞行算法\n;}};六、设计决策流程图当你在设计基类时可以用以下流程决定函数的类型设计一个成员函数 │ ├─ 派生类必须提供自己的实现 │ ├─ 是 → 使用 Pure Virtual ( 0) │ └─ 否 → 继续问 │ ├─ 派生类可能需要不同的实现 │ ├─ 是 → 使用 Impure Virtual (提供默认实现) │ └─ 否 → 使用 Non-Virtual │ └─ 是否担心派生类忘记覆盖 ├─ 是 → 使用 Pure Virtual protected 默认实现 └─ 否 → 使用 Impure Virtual七、总结函数类型继承内容使用场景Pure Virtual仅接口定义派生类必须实现的契约Impure Virtual接口 默认实现提供通用行为允许特殊情况覆盖Non-Virtual接口 强制实现确保所有派生类行为一致请记住接口继承和实现继承不同。在 public 继承之下derived classes 总是继承 base class 的接口。pure virtual 函数只具体指定接口继承。简朴的非纯impure virtual 函数具体指定接口继承及缺省实现继承。non-virtual 函数具体指定接口继承以及强制性实现继承。正确区分这三种函数类型并理解它们背后的设计意图是创建清晰、健壮、可维护的继承体系的基础。每一个 virtual/non-virtual 的选择都应该是有意识的设计决策而不是随意的编码习惯。参考《Effective C》第三版Scott Meyers 著相关条款条款32确定 public 继承塑模出 is-a 关系、条款33避免遮掩继承而来的名字、条款35考虑 virtual 函数以外的其他选择
Effective C++ 条款34:区分接口继承和实现继承
发布时间:2026/6/14 21:12:06
Effective C 条款34区分接口继承和实现继承public 继承看似简单实则包含两个可分离的部分接口继承与实现继承。理解它们的区别是设计出优雅继承体系的关键一步。一、问题的提出继承的两种含义在 C 中当我们写下这样的代码时到底在继承什么classBase{public:virtualvoidfunc1()0;// pure virtualvirtualvoidfunc2();// impure virtualvoidfunc3();// non-virtual};classDerived:publicBase{// Derived 从 Base 继承了什么};表面上看Derived继承了Base的三个函数。但深入思考会发现func1没有实现派生类必须自己实现func2有默认实现派生类可以覆盖也可以不覆盖func3有固定实现派生类不应该覆盖这三种函数代表了三种截然不同的继承语义。混淆它们是继承设计中常见的错误。二、三种成员函数的继承语义2.1 Pure Virtual 函数只继承接口classShape{public:virtual~Shape()default;// Pure virtual 函数只指定接口不指定实现virtualvoiddraw()const0;virtualdoublearea()const0;virtualdoubleperimeter()const0;};classCircle:publicShape{public:Circle(doubleradius):radius_(radius){}// 必须实现所有 pure virtual 函数voiddraw()constoverride{std::cout绘制圆形半径radius_\n;}doublearea()constoverride{return3.14159*radius_*radius_;}doubleperimeter()constoverride{return2*3.14159*radius_;}private:doubleradius_;};classRectangle:publicShape{public:Rectangle(doublew,doubleh):width_(w),height_(h){}voiddraw()constoverride{std::cout绘制矩形width_xheight_\n;}doublearea()constoverride{returnwidth_*height_;}doubleperimeter()constoverride{return2*(width_height_);}private:doublewidth_,height_;};Pure Virtual 函数的设计意图特性说明接口契约定义派生类必须支持的接口强制实现派生类必须提供自己的实现否则无法实例化抽象基类包含 pure virtual 的类是抽象类不能创建对象多态基础为运行时多态提供统一的调用接口令人意外的事实C 允许为 pure virtual 函数提供定义classShape{public:// 声明为 pure virtual但仍可提供默认实现virtualvoiddraw()const0;virtualdoublearea()const0;};// 为 pure virtual 函数提供定义voidShape::draw()const{std::cout默认绘制实现\n;}doubleShape::area()const{return0.0;// 默认面积}classTriangle:publicShape{public:// 可以选择调用基类的默认实现voiddraw()constoverride{Shape::draw();// 调用 pure virtual 的默认实现std::cout三角形自定义绘制\n;}doublearea()constoverride{// 必须提供自己的计算returnbase_*height_/2;}private:doublebase_,height_;};这种有定义的 pure virtual 函数的用途为派生类提供一个可选的默认实现但强制派生类显式决定是否使用它。2.2 Impure Virtual 函数继承接口和默认实现classAnimal{public:virtual~Animal()default;// Impure virtual继承接口 默认实现virtualvoidmakeSound()const{std::cout某种动物叫声\n;// 默认实现}virtualvoidmove()const{std::cout动物在移动\n;// 默认实现}};classDog:publicAnimal{public:// 覆盖默认实现提供特定行为voidmakeSound()constoverride{std::cout汪汪\n;}// move() 继承默认实现不需要覆盖};classCat:publicAnimal{public:voidmakeSound()constoverride{std::cout喵喵~\n;}};classFish:publicAnimal{public:voidmakeSound()constoverride{std::cout...鱼不会叫\n;}voidmove()constoverride{std::cout鱼在游泳\n;}};Impure Virtual 函数的设计意图特性说明接口 默认实现派生类继承函数的签名和默认行为可选覆盖派生类可以选择使用默认实现或提供自己的实现代码复用为大多数派生类提供通用实现减少重复代码2.3 Non-Virtual 函数继承接口和强制实现classBase{public:// Non-virtual 函数接口 强制实现voidalgorithm(){// 算法的框架不允许派生类改变step1();step2();step3();}virtual~Base()default;protected:// 这些步骤派生类可以定制virtualvoidstep1()0;virtualvoidstep2()0;virtualvoidstep3()0;};// 更典型的例子classClock{public:// 获取当前时间所有时钟的统一接口不允许覆盖TimegetCurrentTime()const{returnreadHardwareClock();}// 格式化显示统一的显示方式std::stringformatTime(constTimet)const{returnt.toString(YYYY-MM-DD HH:MM:SS);}private:virtualTimereadHardwareClock()const0;// 硬件相关由派生类实现};classSystemClock:publicClock{private:TimereadHardwareClock()constoverride{// 读取系统时钟returnTime::now();}};classAtomicClock:publicClock{private:TimereadHardwareClock()constoverride{// 读取原子钟returnfetchAtomicTime();}};Non-Virtual 函数的设计意图特性说明不变性表示派生类不应该改变的行为一致性确保所有派生类的某个行为完全一致静态绑定调用在编译期确定性能更好三、三种函数的对比总结classBase{public:// 1. Pure Virtual只继承接口virtualvoidinterfaceOnly()0;// 2. Impure Virtual继承接口 默认实现virtualvoidinterfaceWithDefault(){std::cout默认实现\n;}// 3. Non-Virtual继承接口 强制实现voidinterfaceWithMandatory(){std::cout这是唯一实现不允许覆盖\n;}};函数类型继承接口继承实现派生类能否覆盖设计意图Pure Virtual是否可选项必须覆盖“你必须实现这个功能”Impure Virtual是是默认可选覆盖“你可以使用默认实现也可以自定义”Non-Virtual是是强制不应该覆盖“所有派生类的这个行为必须一致”四、一个完整的实际案例游戏 AI 系统让我们用一个游戏 AI 系统来展示三种函数的正确使用#includeiostream#includestring#includevector#includememory// AI 行为基类定义所有 AI 的通用接口classAIBehavior{public:virtual~AIBehavior()default;// Pure Virtual每个 AI 必须有自己的实现 // 评估当前状态并决定下一步行动virtualvoidevaluate()0;// 执行选定的行动virtualvoidexecute()0;// 获取 AI 的名称virtualstd::stringgetName()const0;// Impure Virtual有默认实现但可覆盖 // 更新 AI 状态每帧调用// 默认实现先评估再执行virtualvoidupdate(floatdeltaTime){evaluate();if(hasActionSelected()){execute();}}// 受到伤害时的反应// 默认实现简单的退缩virtualvoidonDamageTaken(intdamage){std::coutgetName() 受到 damage 点伤害\n;health_-damage;if(health_0){onDeath();}}// Non-Virtual所有 AI 的共同行为不允许改变 // 获取当前生命值所有 AI 的生命值计算方式相同intgetHealth()const{returnhealth_;}// 检查 AI 是否存活统一的判断逻辑boolisAlive()const{returnhealth_0;}// 注册到 AI 管理器统一的注册流程voidregisterToManager(AIManagermanager){manager.registerAI(this);onRegistered();}protected:inthealth_100;boolhasAction_false;boolhasActionSelected()const{returnhasAction_;}// 派生类可以覆盖的钩子virtualvoidonDeath(){std::coutgetName() 死亡。\n;}virtualvoidonRegistered(){}};// 具体的 AI 实现巡逻的守卫classPatrolGuardAI:publicAIBehavior{public:std::stringgetName()constoverride{return巡逻守卫;}voidevaluate()override{// 检查视野内是否有敌人if(detectEnemy()){selectedAction_Action::Attack;hasAction_true;}elseif(shouldPatrol()){selectedAction_Action::Patrol;hasAction_true;}else{hasAction_false;}}voidexecute()override{switch(selectedAction_){caseAction::Attack:std::cout守卫发现敌人发起攻击\n;break;caseAction::Patrol:std::cout守卫继续巡逻...\n;break;}}// 覆盖默认的伤害反应守卫会呼叫支援voidonDamageTaken(intdamage)override{AIBehavior::onDamageTaken(damage);// 先调用默认处理if(isAlive()){std::cout守卫呼叫支援\n;callForBackup();}}private:enumclassAction{Attack,Patrol};Action selectedAction_;booldetectEnemy(){/* ... */returnfalse;}boolshouldPatrol(){/* ... */returntrue;}voidcallForBackup(){/* ... */}};// 具体的 AI 实现Boss 怪物classBossAI:publicAIBehavior{public:std::stringgetName()constoverride{returnBoss;}voidevaluate()override{// Boss 有更复杂的决策逻辑if(health_30){selectedAction_Action::Enrage;hasAction_true;}elseif(canUseSpecialAttack()){selectedAction_Action::SpecialAttack;hasAction_true;}else{selectedAction_Action::NormalAttack;hasAction_true;}}voidexecute()override{switch(selectedAction_){caseAction::Enrage:std::coutBoss 进入狂暴状态\n;attackPower_*2;break;caseAction::SpecialAttack:std::coutBoss 释放必杀技\n;break;caseAction::NormalAttack:std::coutBoss 普通攻击。\n;break;}}// Boss 覆盖默认更新狂暴时更新频率更高voidupdate(floatdeltaTime)override{if(isEnraged_){// 狂暴时更新两次AIBehavior::update(deltaTime);AIBehavior::update(deltaTime);}else{AIBehavior::update(deltaTime);}}private:enumclassAction{Enrage,SpecialAttack,NormalAttack};Action selectedAction_;intattackPower_50;boolisEnraged_false;boolcanUseSpecialAttack(){/* ... */returnfalse;}};// 使用示例classAIManager{public:voidregisterAI(AIBehavior*ai){aiList_.push_back(ai);}voidupdateAll(floatdeltaTime){for(auto*ai:aiList_){if(ai-isAlive()){// Non-virtual统一的存活检查ai-update(deltaTime);// Virtual调用各自的更新逻辑}}}private:std::vectorAIBehavior*aiList_;};设计分析函数类型设计理由evaluate()/execute()/getName()Pure Virtual每种 AI 的行为都不同必须各自实现update()/onDamageTaken()Impure Virtual大多数 AI 使用默认逻辑但特殊 AI如 Boss可以覆盖getHealth()/isAlive()/registerToManager()Non-Virtual所有 AI 的这些行为应该一致不允许改变五、常见陷阱默认实现的危险Impure virtual 函数虽然方便但也存在隐患classAirplane{public:virtualvoidfly(){// 默认实现普通飞机的飞行方式std::cout使用默认飞行算法\n;}};classBoeing747:publicAirplane{// 使用默认的 fly() 实现——合理};classModelC172:publicAirplane{// 使用默认的 fly() 实现——合理};// 危险新增一种飞机忘记覆盖 fly()classSpaceShuttle:publicAirplane{// 糟糕航天飞机不应该使用普通飞机的飞行算法// 但编译器不会报错因为 fly() 有默认实现};解决方案将接口和默认实现分离classAirplane{public:// 纯虚函数只声明接口virtualvoidfly()0;protected:// 默认实现单独提供派生类必须显式选择是否使用voiddefaultFly(){std::cout使用默认飞行算法\n;}};classBoeing747:publicAirplane{public:voidfly()override{defaultFly();// 显式选择使用默认实现}};classSpaceShuttle:publicAirplane{public:voidfly()override{// 必须自己实现无法意外使用默认版本std::cout使用航天飞机专用飞行算法\n;}};六、设计决策流程图当你在设计基类时可以用以下流程决定函数的类型设计一个成员函数 │ ├─ 派生类必须提供自己的实现 │ ├─ 是 → 使用 Pure Virtual ( 0) │ └─ 否 → 继续问 │ ├─ 派生类可能需要不同的实现 │ ├─ 是 → 使用 Impure Virtual (提供默认实现) │ └─ 否 → 使用 Non-Virtual │ └─ 是否担心派生类忘记覆盖 ├─ 是 → 使用 Pure Virtual protected 默认实现 └─ 否 → 使用 Impure Virtual七、总结函数类型继承内容使用场景Pure Virtual仅接口定义派生类必须实现的契约Impure Virtual接口 默认实现提供通用行为允许特殊情况覆盖Non-Virtual接口 强制实现确保所有派生类行为一致请记住接口继承和实现继承不同。在 public 继承之下derived classes 总是继承 base class 的接口。pure virtual 函数只具体指定接口继承。简朴的非纯impure virtual 函数具体指定接口继承及缺省实现继承。non-virtual 函数具体指定接口继承以及强制性实现继承。正确区分这三种函数类型并理解它们背后的设计意图是创建清晰、健壮、可维护的继承体系的基础。每一个 virtual/non-virtual 的选择都应该是有意识的设计决策而不是随意的编码习惯。参考《Effective C》第三版Scott Meyers 著相关条款条款32确定 public 继承塑模出 is-a 关系、条款33避免遮掩继承而来的名字、条款35考虑 virtual 函数以外的其他选择