Item 36:绝不重新定义继承而来的 non-virtual 函数
对齐 Effective C++ 第三版 Item 36:证明 non-virtual 函数按静态类型绑定,derived 同名声明不是 override,会让同一对象经 Base pointer 与 Derived pointer 表现不同,并选择 virtual、NVI、改名或组合修复。
学习目标
- 能复现同一 Derived object 经 Base pointer 与 Derived pointer 调用不同 non-virtual bodies
- 能解释 non-virtual 声明表示接口加强制实现,derived 同名函数为何破坏 public is-a
- 能判断保持 base body、改成 virtual/NVI、改名、composition 或静态 policy 的修复边界
dispatch 诊断实验
先预测:这次调用会落到哪个 body?
先写出表达式的 static type 和对象的 dynamic type,再切换视图查看 dispatch 证据。
观察
同一个 Derived object 同时被 Base* 与 Derived* 观察;两个表达式看似调用同一个 mf,实际可能选不同 body。
决策
Base* 视图必须继续满足 Base contract;不能要求所有调用者“记得”使用 Derived* 才得到业务语义。
当前诊断 · base pointer / derived object
Base* 视图必须继续满足 Base contract;不能要求所有调用者“记得”使用 Derived* 才得到业务语义。
从同一个对象出现两种行为开始
class Base {
public:
void mf() const {
log("Base::mf");
}
};
class Derived : public Base {
public:
void mf() const {
log("Derived::mf");
}
};Derived::mf 看起来像 override,但 Base::mf 不是 virtual。两个 calls 的结果由表达式 static type 决定:
Derived object;
Base* baseView = &object;
Derived* derivedView = &object;
baseView->mf(); // Base::mf
derivedView->mf(); // Derived::mfItem 36 的原则是 Never redefine an inherited non-virtual function(绝不重定义继承 non-virtual 函数)。
↡derived 声明与 inherited non-virtual 同名同签名函数,造成静态入口分裂的行为。static type 与 dynamic type 分工不同
baseView 的 static type 是 Base*,dynamic object 是 Derived。virtual function 会按 dynamic type dispatch;non-virtual 早在 compile time 选定 Base::mf。
↡编译期从变量、表达式或声明确定、用于 ordinary lookup 与 non-virtual binding 的类型。 ↡运行期对象实际最派生 class,用于 virtual dispatch 与 RTTI。 ↡通过 Base pointer/reference 观察实际 Derived object 的表达式。 ↡以 Derived pointer/reference 保留具体静态类型的表达式。只改变指针声明,就让同一 object 执行不同语义。这不是正常 polymorphism。
non-virtual 表示行为必须统一
Item 34 已建立:public non-virtual function 同时继承 interface 与 mandatory implementation。Base author 在说“每个 Base subtype 都应这样做”。
↡base non-virtual body 对所有 public derived 强制生效的实现政策。如果 Derived 确实 is-a Base,那么 Base::mf 的 contract 对 Derived 也必须成立。Derived 不能再用同名入口声明另一个政策。
↡public inheritance 要求 derived 在所有 base clients 中保持相同行为契约。同名声明只是 hiding,不是 overriding
Derived::mf 会在 Derived scope 隐藏 Base::mf;它没有 vtable slot,也不参与 virtual override。
↡derived 同名 declaration 让普通查找不再看到 base name,但没有动态分派关系。override 能把误解变成 compile error:
class Derived : public Base {
public:
void mf() const override; // error: Base::mf is not virtual
};所有 intended override 都应写 override;这能捕获 base 忘记 virtual、签名漂移和 const mismatch。
using declaration 不能改变 binding
using Base::mf 只改变 name visibility/overload set,不会把 Base::mf 变 virtual,也不会让 Derived::mf 动态分派。
class Derived : public Base {
public:
using Base::mf;
void mf() const; // same signature still hides/selects in Derived scope
};Item 33 解决“哪些 names 可见”,本章解决“call body 何时确定”;两层不能互相替代。
public is-a 因视图变化而破坏
若 process(Base&) 是合法 base client,它调用 mf 时必须得到 Base contract。Derived direct client 得到另一 contract,意味着 Derived 不再有单一可替换语义。
void process(const Base& value) {
value.mf();
}
Derived d;
process(d); // Base::mf
d.mf(); // Derived::mf这正违反 Item 32 的 behavioral substitutability。
若所有 subtype 行为相同,删除 derived 函数
最简单修复是继承 Base implementation:
class Derived : public Base {
// no mf declaration
};如果 Derived 只想在调用前后做额外工作,应使用不同名称的 operation 或由调用者显式组合,不能悄悄改变 inherited mf。
↡新行为使用不同语义名称,避免被误读为 inherited contract customization。名称应表达新 contract,而不是复用 mf 制造隐藏。
若行为应随 subtype 变化,base 必须声明 virtual
如果 Base contract 本来就允许 customization,修改 base:
class Base {
public:
virtual ~Base() = default;
virtual void mf() const = 0;
};
class Derived : public Base {
public:
void mf() const override;
};这会改变 ABI、destructor/ownership 和 performance contract,公共库需版本策略。
NVI 保留统一外壳并允许步骤变化
如果 public mf 的 validation、锁、审计必须一致,只让核心步骤变化:
class Base {
public:
void mf() const {
validate();
doMf();
audit();
}
private:
virtual void doMf() const = 0;
};Derived override doMf,不声明 mf;既保持 base invariant,又提供动态算法。
不能修改 legacy base 时用组合或 adapter
第三方 Base 的 mf non-virtual,业务需要不同 operation,不能安全 public derive 后同名重定义。
↡对象持有 legacy Base 并以新 public contract 显式委托或扩展的 wrapper。class SpecializedService {
public:
void specializedOperation() {
base_.mf();
applySpecialRules();
}
private:
Base base_;
};客户不会把 SpecializedService 当 Base,因此没有 static-view split。
qualified base call 是 virtual override 内的显式复用
Derived virtual override 可以故意调用 Base::mf 复用 base body;qualified call 明确绕过 dynamic dispatch。这与重定义 non-virtual 不同。
↡override 内通过 `Base::mf()` 显式调用 base implementation 作为算法一部分。void Derived::mf() const {
Base::mf();
extendBehavior();
}前提是 Base::mf 本身 virtual customization contract 合法;qualified call 不能把 nonvirtual split 变正确。
final 可表达不允许继续变化
若 virtual hierarchy 到某层需要固定 implementation,可将 override 标 final:
void mf() const final;这比把同名 non-virtual function 塞进 derived 更清楚,compiler 能检查。
↡通过 virtual/override/final 形成可由编译器验证的 variation policy。如果整个 class 不应被继承,可声明 class final。
CRTP/static polymorphism 是不同契约
CRTP 可让 base template 静态调用 derived implementation:
template<class Derived>
class StaticBase {
public:
void mf() const {
static_cast<const Derived&>(*this).doMf();
}
};每个 StaticBase<Derived> 是不同类型;它不承诺把任意 derived object 经一个非模板 Base* runtime 替换。
因此 static polymorphism 不等于“在普通 public inheritance 中重定义 nonvirtual 合法”。
overload 与同签名 redefinition 要分开
Derived 新增 mf(int) 会隐藏 Base::mf(),属于 Item 33 的 name lookup 问题;Derived 新增相同 mf() 则同时产生本章行为分裂。
using 可修复前者的可见性,无法修复后者的 dispatch 语义。
用双视图测试固定行为
先预测每个 call 的 static type 与 selected body,再建立门禁:
- 对同一 Derived object 通过 Base*/Base&/Derived*/Derived& 调用 operation。
- compile check 要求 intended customization 使用 override;nonvirtual 上 override 必须失败。
- 搜索 derived 与 base 同签名 non-virtual declarations,作为 lint error。
- base contract suite 经每个 subtype factory 重跑。
- virtual repair 后所有 views 命中 Derived override,并覆盖 virtual destructor。
- NVI repair 证明所有 views 经过 wrapper,hook 前后 invariant 不遗漏。
- composition adapter compile test 证明不能隐式转换为 legacy Base。
- ABI tests 覆盖把 public base function 改 virtual 的版本影响。
只有所有合法 views 观察到同一 public contract,继承设计才成立。
小结
- non-virtual function 静态绑定,selected body 由 pointer/reference/expression static type 决定
- derived 同名同签名 function 只是 hiding/redefinition,不是 override
- 同一 Derived object 经 Base* 调 Base body、经 Derived* 调 Derived body,破坏 is-a substitutability
- using declaration 只能恢复名称可见性,不能把 nonvirtual 变成 dynamic dispatch
- 行为不变就删除 derived function;合法变化就让 base virtual 或采用 NVI
- legacy base 不可改时使用 composition/adapter,static polymorphism 则是另一套显式 compile-time contract
名词解释
本章出现的专业名词,用大白话再讲一遍。
- static binding
按表达式静态类型选择 nonvirtual body。
- non-virtual redefinition
derived 同签名声明形成另一静态入口。
- static type
编译期决定 lookup 和静态绑定的类型。
- dynamic type
- 运行期实际最派生对象类型。
- base pointer view
经 Base pointer/reference 观察 derived object。
- derived object view
保留 Derived 静态类型的调用视图。
- mandatory inherited implementation
全部 subtype 统一使用的 base nonvirtual 政策。
- is-a behavior preservation
derived 保持全部 base 客户行为。
- non-virtual name hiding
同名 derived 声明隐藏 base 名称。
- override contract check
compiler 验证真实 virtual override。
- visibility-only using declaration
using 只改可见性不改 binding。
- lookup-dispatch confusion
以查名工具误修 dispatch 问题。
- static-view behavior split
不同静态视图产生不同语义。
- substitution inconsistency
base 与 concrete 客户观察不兼容结果。
- inherited policy acceptance
derived 接受 base nonvirtual 实现。
- distinct derived operation
以不同名称表达新 contract。
- intentional virtual variation
base 明确声明的动态 customization。
- dynamic-view consistency
所有静态 views 命中同一 override。
- NVI repair
固定 public wrapper 加 virtual hook 的修复。
- uniform public entry
所有调用经过同一 nonvirtual 外壳。
- legacy composition adapter
持有并委托不可修改 base 的 wrapper。
- composition repair
以 has-a 复用而不声明 is-a。
- qualified base reuse
override 中显式调用 base body。
- override composition
动态 override 内组合 base implementation。
- final override boundary
禁止进一步 override 的终点。
- checked dispatch policy
virtual/override/final 的可检查政策。
- static polymorphism
template static type 驱动的多态。
- explicit static dispatch contract
不依赖统一 runtime Base* 的静态契约。
- derived overload hiding
不同参数同名 derived function 的遮蔽。
- same-signature static split
同签名 nonvirtual 形成两个静态 bodies。
- static-binding dispatch matrix
静态/动态类型与选中 body 的测试表。
练习
- 问题 1:static binding 与 base pointer / derived object 双视图。 Base/Derived 都声明 nonvirtual
normalize(),同一 Derived object 经两种 pointers 调用,请解释并修复。
- 问题 2:redefine an inherited non-virtual function 与第三方 Base。 业务需要扩展 mf,但库的 Base::mf nonvirtual,团队计划 public derive 同名覆盖。
- 问题 3:static binding、using 与 CRTP。 团队认为
using Base::mf可修复同签名 redefinition,另有人用 CRTP 证明 nonvirtual 可多态,请澄清。