虚函数重写必须函数名、参数类型列表、const/volatile限定符、ref-qualifier完全一致;返回类型仅允许协变,即base→derived或base&→derived&,且derived须公有继承自base。

虚函数重写必须满足哪些签名条件
重写(override)不是“名字一样就行”,C++ 要求 函数名、参数类型列表、const/volatile 限定符、ref-qualifier(& 或 &&) 必须完全一致,否则编译器当它是新函数(重载),不是重写。返回类型是唯一允许“不完全相同”的地方——但仅限协变返回类型。
常见错误现象:Base* 返回却在派生类里写成 Derived*,但没用指针/引用;或返回 std::unique_ptr<base> 却想重写为 std::unique_ptr<derived></derived> —— 这些都不协变,直接报错 error: 'func' does not override any member function。
- 协变只适用于指针和引用类型:比如
Base*→Derived*,或Base&→Derived&,且Derived必须公有继承自Base - 返回类型不能是值类型(如
Base→Derived)、也不能是智能指针、容器、函数类型等——它们不满足“可安全转换 + 地址兼容”要求 - 参数列表哪怕差一个
const(比如void f(int)vsvoid f(const int)),就不算重写(因为顶层 const 不影响类型)
为什么只允许指针/引用类型协变
根本原因是对象布局和多态调用的安全性。虚函数调用时,编译器生成的代码依赖基类接口约定的返回值大小、内存对齐、析构行为。指针/引用的大小和调用约定在继承体系中是统一的(都是 8 字节、都支持多态析构),而值类型会触发复制、可能调用派生类析构函数去清理基类对象,破坏 ABI 稳定性。
举个典型场景:你写 virtual Base* clone() const,派生类实现 Derived* clone() const。用户用 Base* p = obj.clone(),实际拿到的是 Derived*,但能安全赋给 Base*,且后续通过 p->some_virtual_func() 仍能正确分发到 Derived 版本——这正是协变设计要保的契约。
立即学习“C++免费学习笔记(深入)”;
- 协变返回类型在 ABI 层面没有额外开销:返回寄存器(如
%rax)传的仍是地址,无需调整 - 如果允许
Base→Derived值返回,就涉及对象切片或隐式构造,无法保证虚表指针被正确设置 -
std::shared_ptr<base>看似“像指针”,但它不是语言内置的协变类型,C++ 标准没为此特设规则,所以不能协变
override 关键字怎么帮你看穿重写失败
不加 override,编译器不会主动检查是否真构成了重写;加了,它就会严格比对基类虚函数签名,稍有不符立刻报错。这是防止“以为重写了结果只是新增了一个重载函数”的最有效手段。
常见错误现象:基类函数是 virtual void f(int&) const,派生类写成 void f(const int&) const override —— 参数类型不一致,override 直接让编译失败,而不是静默接受。
- 永远在你“打算重写”的函数声明末尾加上
override,别省 - 如果加了
override却编译不过,说明签名没对齐:逐项核对const、&、参数类型(注意int和int&是不同类型)、函数名拼写 - 基类函数没声明
virtual?那加override也会报错——这是提醒你:它根本不是虚函数,重写无意义
协变返回类型在模板类里能不能用
能,但受限于模板实例化时机。只要模板参数能推导出明确的继承关系,协变就生效;但如果继承关系依赖未确定的模板参数(比如 T 是否继承自 U 尚未可知),编译器无法验证协变合法性,就会拒绝。
使用场景:写泛型工厂或克隆接口时,比如 template<typename t> struct Clonable { virtual T* clone() const = 0; };</typename>,派生类 struct D : Clonable<d> { D* clone() const override; }</d> 是合法的,因为 D 显式作为模板实参,继承关系明确。
- 别试图在模板中写
virtual Base<t>* f()</t>→Derived<t>* f() override</t>,除非你能保证Derived<t></t>公有继承自Base<t></t>,且该关系在实例化点可见 - 协变不跨模板实例:
Base<int>*</int>和Base<double>*</double>之间不存在协变关系,哪怕它们都叫Base - 如果编译器报
error: covariant return type must be a class type derived from ...,大概率是模板推导出了非类类型,或继承关系未被正确定义(比如用了私有继承)
协变看着宽松,其实每条限制都在堵住二进制层面的漏洞。最容易被忽略的是:它只认语言原生的指针/引用语义,不买任何封装类型的账——哪怕那个封装类型行为上“几乎就是指针”。










