C++ 标准在讨论 glvalue 时,经常会看到类似说法:
If a glvalue refers to a polymorphic object, its dynamic type may differ from its static type.
中文:
如果一个 glvalue 指代一个多态对象,它的动态类型可能不同于它的静态类型。
,这样说, 这句话作为"提醒存在这种情形"是对的,但它不是标准原文,而且把一个非必要条件写成了条件句,会误导。
标准的动态类型定义与"多态"无关,非多态对象同样会出现动态类型不同于静态类型的情形。
标准原文
动态类型的正式定义在 defns.dynamic.type,原文是:
⟨glvalue⟩ type of the most derived object to which the glvalue refers
配的例子是:若 p 是 "pointer to class B" 且指向 B 的基类子对象,而该子对象的最派生对象是 D,则 *p 的动态类型是 "D",并补一句 "References are treated similarly."
关键在于:这个条目通篇没有出现 "polymorphic" 或 "virtual"。定义的谓语只依赖"glvalue 所指示对象的最派生对象",不依赖多态性。
静态类型在 defns.static.type:
type of an expression resulting from analysis of the program without considering execution semantics
它只由程序形式决定,运行期不变。而多态类的定义在 class.virtual/1:
A class with a virtual member function is called a polymorphic class.
多态不是必要条件
cpp
struct B {}; // 非多态
struct D : B {}; // 非多态
D d;
B* p = &d;
// *p: 静态类型 B, 动态类型 D
*p 是 glvalue,指代 d 的 B 基类子对象;该子对象的 most derived object 是 d,类型 D。于是 D ≠ B,而 B 没有任何虚函数。
反过来说,"多态"也不是充分刻画动态类型的工具,它只是说明能否在运行期观测或使用动态类型。
"多态"真正起作用的地方
typeid:按 expr.typeid,只有当操作数是多态类类型的 glvalue 时,结果才表示最派生对象的类型(即动态类型);否则一律表示表达式的静态类型。dynamic_cast:向下/交叉转换按 expr.dynamic.cast/5 要求操作数是多态类型的指针或 glvalue,否则编译错误;向上转换(/4)在编译期解析,不需要多态。- 虚函数分派:只有多态类才有虚表,因而只有它的运行期行为依赖动态类型。
更准确的说法
cppreference 的原话(Value categories):
A glvalue may be polymorphic: the dynamic type of the object it identifies is not necessarily the static type of the expression.
翻译过来是:glvalue 可以是多态的,即它所标识对象的动态类型未必是表达式的静态类型。对应的反面陈述是 prvalue 不可能是多态的,其动态类型恒等于表达式的类型。
所以如果你要写定义,应说:
任何 glvalue 都有动态类型,即其所指代对象的最派生对象的类型,它可以不同于表达式的静态类型
而不是把"多态"设为前提。