virtual
function specifier
指定某个非静态 成员函数 为 虚函数 并支持动态派发。该说明符仅可出现在非静态成员函数首次声明的 声明说明符序列 中(即在类定义内声明时)。
目录 |
说明
虚函数是那些行为可以在派生类中被重写的成员函数。与非虚函数不同,即使没有关于类实际类型的编译时信息,这种重写行为仍然保留。也就是说,如果通过基类的指针或引用处理派生类,调用被重写的虚函数将会调用派生类中定义的行为。这样的函数调用被称为
虚函数调用
或
虚调用
。如果使用
限定名称查找
选择函数(即函数名出现在作用域解析运算符
::
右侧),则虚函数调用会被抑制。
#include <iostream> struct Base { virtual void f() { std::cout << "base\n"; } }; struct Derived : Base { void f() override // 'override' 是可选的 { std::cout << "derived\n"; } }; int main() { Base b; Derived d; // 通过引用调用虚函数 Base& br = b; // br 的类型是 Base& Base& dr = d; // dr 的类型也是 Base& br.f(); // 输出 "base" dr.f(); // 输出 "derived" // 通过指针调用虚函数 Base* bp = &b; // bp 的类型是 Base* Base* dp = &d; // dp 的类型也是 Base* bp->f(); // 输出 "base" dp->f(); // 输出 "derived" // 非虚函数调用 br.Base::f(); // 输出 "base" dr.Base::f(); // 输出 "base" }
详细说明
如果某个成员函数
vf
在类
Base
中被声明为
virtual
,并且某个直接或间接派生自
Base
的类
Derived
包含一个具有相同声明的成员函数
- 名称
- 参数类型列表(但不包括返回类型)
- cv限定符
- 引用限定符
那么类
Derived
中的这个函数同样是
virtual
(无论其声明中是否使用关键字
virtual
)并且
overrides
Base::vf(无论其声明中是否使用说明符
override
)。
Base::vf
无需可访问或可见即可被重写。(
Base::vf
可声明为私有,或可通过私有继承方式继承
Base
。在继承
Base
的
Derived
类中,其基类中任何同名称成员均不影响重写判定,即使这些成员会在名称查找时隐藏
Base::vf
。)
class B { virtual void do_f(); // 私有成员 public: void f() { do_f(); } // 公共接口 }; struct D : public B { void do_f() override; // 重写 B::do_f }; int main() { D d; B* bp = &d; bp->f(); // 内部调用 D::do_f(); }
对于每个虚函数,都存在一个
最终覆盖函数
,在虚函数调用时执行该函数。基类
Base
的虚成员函数
vf
就是最终覆盖函数,除非派生类声明或(通过多重继承)继承了另一个覆盖
vf
的函数。
struct A { virtual void f(); }; // A::f 是虚函数 struct B : A { void f(); }; // B::f 在 B 中重写 A::f struct C : virtual B { void f(); }; // C::f 在 C 中重写 A::f struct D : virtual B {}; // D 未引入重写函数,B::f 在 D 中是最终版本 struct E : C, D // E 未引入重写函数,C::f 在 E 中是最终版本 { using A::f; // 非函数声明,仅使 A::f 在查找中可见 }; int main() { E e; e.f(); // 虚函数调用执行 C::f(e 中的最终重写函数) e.E::f(); // 非虚函数调用执行 A::f(在 E 中可见) }
如果一个函数有多个最终覆盖者,则程序是非良构的:
struct A { virtual void f(); }; struct VB1 : virtual A { void f(); // 重写 A::f }; struct VB2 : virtual A { void f(); // 重写 A::f }; // struct Error : VB1, VB2 // { // // 错误:A::f 在 Error 中有两个最终重写器 // }; struct Okay : VB1, VB2 { void f(); // 正确:这是 A::f 的最终重写器 }; struct VB1a : virtual A {}; // 未声明重写器 struct Da : VB1a, VB2 { // 在 Da 中,A::f 的最终重写器是 VB2::f };
具有相同名称但参数列表不同的函数不会覆盖基类中的同名函数,而是会将其 隐藏 :当 非限定名称查找 检查派生类的作用域时,查找会找到该声明而不会检查基类。
struct B { virtual void f(); }; struct D : B { void f(int); // D::f 隐藏了 B::f(参数列表不匹配) }; struct D2 : D { void f(); // D2::f 重写了 B::f(即使它在当前作用域不可见) }; int main() { B b; B& b_as_b = b; D d; B& d_as_b = d; D& d_as_d = d; D2 d2; B& d2_as_b = d2; D& d2_as_d = d2; b_as_b.f(); // 调用 B::f() d_as_b.f(); // 调用 B::f() d2_as_b.f(); // 调用 D2::f() d_as_d.f(); // 错误:在 D 中查找仅找到 f(int) d2_as_d.f(); // 错误:在 D 中查找仅找到 f(int) }
|
若函数声明使用
struct B { virtual void f(int); }; struct D : B { virtual void f(int) override; // OK, D::f(int) 覆写 B::f(int) virtual void f(long) override; // 错误:f(long) 未覆写 B::f(int) };
若函数声明使用
struct B { virtual void f() const final; }; struct D : B { void f() const; // 错误:D::f 试图覆写 final 函数 B::f }; |
(since C++11) |
非成员函数和静态成员函数不能是虚函数。
函数模板不能被声明为
virtual
。这一限制仅适用于作为模板的函数——类模板中的常规成员函数可以被声明为虚函数。
|
虚函数(无论是声明为虚函数还是重写虚函数)不能具有任何关联约束。 struct A { virtual void f() requires true; // 错误:受约束的虚函数 };
|
(C++20 起) |
默认实参 在编译时完成对虚函数的替换。
协变返回类型
如果函数
Derived::f
重写了函数
Base::f
,则它们的返回类型必须相同或是
协变
的。当两个类型满足以下所有要求时,它们才是协变的:
- 两种类型均为指向类的指针或引用(左值或右值)。不允许多级指针或引用。
-
Base::f()返回类型中被引用/指向的类,必须是Derived::f()返回类型中被引用/指向类的明确且可访问的直接或间接基类。 -
Derived::f()的返回类型必须与Base::f()的返回类型具有相等或更少的 cv限定符 。
Derived::f
返回类型中的类必须是
Derived
本身,或者必须在
Derived::f
声明处是一个
完整类型
。
当进行虚函数调用时,最终覆盖函数的返回类型会被 隐式转换 为所调用被覆盖函数的返回类型:
class B {}; struct Base { virtual void vf1(); virtual void vf2(); virtual void vf3(); virtual B* vf4(); virtual B* vf5(); }; class D : private B { friend struct Derived; // 在Derived中,B是D的可访问基类 }; class A; // 前向声明类是不完整类型 struct Derived : public Base { void vf1(); // 虚函数,重写Base::vf1() void vf2(int); // 非虚函数,隐藏Base::vf2() // char vf3(); // 错误:试图重写Base::vf3,但返回类型 // 不同且不满足协变要求 D* vf4(); // 重写Base::vf4()且具有协变返回类型 // A* vf5(); // 错误:A是不完整类型 }; int main() { Derived d; Base& br = d; Derived& dr = d; br.vf1(); // 调用Derived::vf1() br.vf2(); // 调用Base::vf2() // dr.vf2(); // 错误:vf2(int)隐藏了vf2() B* p = br.vf4(); // 调用Derived::vf4()并将结果转换为B* D* q = dr.vf4(); // 调用Derived::vf4()且不将结果转换为B* }
虚析构函数
尽管析构函数不会被继承,但如果基类将其析构函数声明为
virtual
,派生类的析构函数始终会覆盖它。这使得通过基类指针删除动态分配的多态类型对象成为可能。
class Base { public: virtual ~Base() { /* 释放Base的资源 */ } }; class Derived : public Base { ~Derived() { /* 释放Derived的资源 */ } }; int main() { Base* b = new Derived; delete b; // 对Base::~Base()进行虚函数调用 // 由于是虚函数,会调用Derived::~Derived()来释放派生类资源 // 然后按照常规析构顺序调用Base::~Base() }
此外,若基类的析构函数非虚,则通过基类指针删除派生类对象将导致 未定义行为 ——无论是否存在因未调用派生类析构函数而可能泄漏的资源 ,除非选定的释放函数为销毁式 operator delete (C++20 起) 。
一个有用的准则是:当涉及删除表达式时(例如在 std::unique_ptr 中隐式使用的情况 (C++11 起) ),任何基类的析构函数必须是 公开且虚函数或受保护且非虚函数 。
构造与析构期间
当虚函数在构造函数或析构函数中直接或间接被调用时(包括在类的非静态数据成员构造或析构期间,例如在成员 初始化列表 中),且该调用所适用的对象是正在构造或析构的对象,则被调用的函数是构造函数或析构函数所在类中的最终覆盖函数,而非在更派生类中覆盖它的函数。 换言之,在构造或析构期间,更派生的类并不存在。
当构造具有多个分支的复杂类时,在属于某个分支的构造函数内,多态性被限制在该类及其基类范围内:若此时获取到该子层次结构之外的基类子对象指针或引用,并尝试调用虚函数(例如通过显式成员访问),则行为未定义:
struct V { virtual void f(); virtual void g(); }; struct A : virtual V { virtual void f(); // A::f 是 V::f 在 A 中的最终覆盖函数 }; struct B : virtual V { virtual void g(); // B::g 是 V::g 在 B 中的最终覆盖函数 B(V*, A*); }; struct D : A, B { virtual void f(); // D::f 是 V::f 在 D 中的最终覆盖函数 virtual void g(); // D::g 是 V::g 在 D 中的最终覆盖函数 // 注意:A 在 B 之前初始化 D() : B((A*) this, this) {} }; // B 的构造函数,从 D 的构造函数中调用 B::B(V* v, A* a) { f(); // 对 V::f 的虚调用(尽管 D 具有最终覆盖函数,但此时 D 对象尚未构造完成) g(); // 对 B::g 的虚调用,这是 B 中的最终覆盖函数 v->g(); // v 的类型 V 是 B 的基类,虚调用会像之前一样调用 B::g a->f(); // a 的类型 A 不是 B 的基类,它属于继承层次结构的不同分支。 // 通过该分支进行虚调用会导致未定义行为,即使在此情况下 A 已完全构造 //(由于在 D 的基类列表中 A 出现在 B 之前,因此 A 先于 B 构造)。 // 实际上,将尝试使用 B 的虚函数表进行对 A::f 的虚调用, // 因为这是在 B 的构造期间活跃的虚表) }
关键词
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 适用标准 | 发布时行为 | 正确行为 |
|---|---|---|---|
| CWG 258 | C++98 |
派生类的非const成员函数可能因其基类的
const虚成员函数而成为虚函数 |
虚函数还要求cv限定符相同 |
| CWG 477 | C++98 | 友元声明可以包含 virtual 说明符 | 不允许 |
| CWG 1516 | C++98 |
未提供术语"virtual function call"
和"virtual call"的定义 |
已提供 |
另请参阅
| 派生类与继承模式 | |
override
说明符
(C++11)
|
显式声明某方法重写另一方法 |
final
说明符
(C++11)
|
声明某方法不可被重写或某类不可被继承 |