virtual
函数说明符
virtual
说明符指定非静态 成员函数 是 虚函数 并支持动态调度。它只能出现在非静态成员函数的初始声明的 decl-specifier-seq
中(即在类定义中声明时)。
内容 |
[编辑] 解释
虚函数是可以在派生类中重写其行为的成员函数。与非虚函数不同,即使没有关于类的实际类型的编译时信息,重写行为也会保留下来。也就是说,如果派生类使用基类的指针或引用进行处理,对重写虚函数的调用将调用派生类中定义的行为。这种函数调用称为 虚函数调用 或 虚调用。如果使用 限定名称查找 选择函数(即函数名称出现在作用域解析运算符 ::
的右侧),则虚函数调用会被抑制。
#include <iostream> struct Base { virtual void f() { std::cout << "base\n"; } }; struct Derived : Base { void f() override // 'override' is optional { std::cout << "derived\n"; } }; int main() { Base b; Derived d; // virtual function call through reference Base& br = b; // the type of br is Base& Base& dr = d; // the type of dr is Base& as well br.f(); // prints "base" dr.f(); // prints "derived" // virtual function call through pointer Base* bp = &b; // the type of bp is Base* Base* dp = &d; // the type of dp is Base* as well bp->f(); // prints "base" dp->f(); // prints "derived" // non-virtual function call br.Base::f(); // prints "base" dr.Base::f(); // prints "base" }
[编辑] 详细说明
如果某个成员函数 vf
在类 Base
中被声明为 virtual
,并且某个类 Derived
(直接或间接从 Base
派生)具有具有相同
- 名称
- 参数类型列表(但不包括返回类型)
- cv 限定符
- 引用限定符
那么类 Derived
中的此函数也是 虚函数(无论其声明中是否使用关键字 virtual
)并且 重写 Base::vf(无论其声明中是否使用单词 override
)。
Base::vf
无需可访问或可见即可被重写。(Base::vf
可以被声明为私有的,或者 Base
可以使用私有继承进行继承。Derived
的基类中任何具有相同名称的成员(继承 Base
)与重写确定无关,即使它们在名称查找期间会隐藏 Base::vf
。)
class B { virtual void do_f(); // private member public: void f() { do_f(); } // public interface }; struct D : public B { void do_f() override; // overrides B::do_f }; int main() { D d; B* bp = &d; bp->f(); // internally calls D::do_f(); }
对于每个虚函数,都存在 最终重写器,它在进行虚函数调用时执行。基类 Base
的虚成员函数 vf
是最终重写器,除非派生类声明或继承(通过多重继承)另一个重写 vf
的函数。
struct A { virtual void f(); }; // A::f is virtual struct B : A { void f(); }; // B::f overrides A::f in B struct C : virtual B { void f(); }; // C::f overrides A::f in C struct D : virtual B {}; // D does not introduce an overrider, B::f is final in D struct E : C, D // E does not introduce an overrider, C::f is final in E { using A::f; // not a function declaration, just makes A::f visible to lookup }; int main() { E e; e.f(); // virtual call calls C::f, the final overrider in e e.E::f(); // non-virtual call calls A::f, which is visible in E }
如果某个函数有多个最终重写器,则程序格式不正确
struct A { virtual void f(); }; struct VB1 : virtual A { void f(); // overrides A::f }; struct VB2 : virtual A { void f(); // overrides A::f }; // struct Error : VB1, VB2 // { // // Error: A::f has two final overriders in Error // }; struct Okay : VB1, VB2 { void f(); // OK: this is the final overrider for A::f }; struct VB1a : virtual A {}; // does not declare an overrider struct Da : VB1a, VB2 { // in Da, the final overrider of A::f is VB2::f };
具有相同名称但参数列表不同的函数不会重写具有相同名称的基函数,而是 隐藏 它:当 非限定名称查找 检查派生类的范围时,查找会找到声明并且不会检查基类。
struct B { virtual void f(); }; struct D : B { void f(int); // D::f hides B::f (wrong parameter list) }; struct D2 : D { void f(); // D2::f overrides B::f (doesn't matter that it's not visible) }; 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(); // calls B::f() d_as_b.f(); // calls B::f() d2_as_b.f(); // calls D2::f() d_as_d.f(); // Error: lookup in D finds only f(int) d2_as_d.f(); // Error: lookup in D finds only f(int) }
如果函数声明带有 struct B { virtual void f(int); }; struct D : B { virtual void f(int) override; // OK, D::f(int) overrides B::f(int) virtual void f(long) override; // Error: f(long) does not override B::f(int) }; 如果函数声明带有 struct B { virtual void f() const final; }; struct D : B { void f() const; // Error: D::f attempts to override final B::f }; |
(自 C++11 起) |
非成员函数和静态成员函数不能是虚函数。
函数模板不能声明为 virtual
。这仅适用于本身为模板的函数——类模板的常规成员函数可以声明为虚函数。
虚函数(无论是否声明为虚函数或重写一个虚函数)都不能有任何关联的约束。 struct A { virtual void f() requires true; // Error: constrained virtual function };
|
(自 C++20 起) |
虚函数的 默认参数 在编译时被替换。
[edit] 协变返回类型
如果函数 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; // in Derived, B is an accessible base of D }; class A; // forward-declared class is an incomplete type struct Derived : public Base { void vf1(); // virtual, overrides Base::vf1() void vf2(int); // non-virtual, hides Base::vf2() // char vf3(); // Error: overrides Base::vf3, but has different // and non-covariant return type D* vf4(); // overrides Base::vf4() and has covariant return type // A* vf5(); // Error: A is incomplete type }; int main() { Derived d; Base& br = d; Derived& dr = d; br.vf1(); // calls Derived::vf1() br.vf2(); // calls Base::vf2() // dr.vf2(); // Error: vf2(int) hides vf2() B* p = br.vf4(); // calls Derived::vf4() and converts the result to B* D* q = dr.vf4(); // calls Derived::vf4() and does not convert the result to B* }
[edit] 虚析构函数
即使析构函数不是继承的,如果基类声明其析构函数为 virtual
,派生析构函数总是覆盖它。 这使得可以通过指向基类的指针删除动态分配的多态类型对象成为可能。
class Base { public: virtual ~Base() { /* releases Base's resources */ } }; class Derived : public Base { ~Derived() { /* releases Derived's resources */ } }; int main() { Base* b = new Derived; delete b; // Makes a virtual function call to Base::~Base() // since it is virtual, it calls Derived::~Derived() which can // release resources of the derived class, and then calls // Base::~Base() following the usual order of destruction }
此外,如果基类的析构函数不是虚函数,则通过指向基类的指针删除派生类对象的行为是 *未定义的行为*,无论是否存在如果未调用派生析构函数就会泄漏的资源,除非所选的释放函数是销毁 operator delete(自 C++20 起)。
一个有用的指南是,任何基类的析构函数必须是 公共且虚函数或受保护且非虚函数,只要涉及 delete 表达式,例如在隐式使用 std::unique_ptr(自 C++11 起)。
[edit] 在构造和析构期间
当直接或间接地从构造函数或析构函数(包括在类的非静态数据成员的构造或析构期间,例如在成员 初始化列表 中)调用虚函数,并且该调用所应用的对象是正在构造或析构的对象时,调用的函数是构造函数或析构函数类的最终覆盖者,而不是在更派生类中覆盖它的函数。 换句话说,在构造或析构期间,更派生类不存在。
当构造一个具有多个分支的复杂类时,在一个属于某个分支的构造函数中,多态性仅限于该类及其基类:如果它获得了一个指向该子层次结构之外的基子对象的指针或引用,并尝试调用虚函数调用(例如,使用显式成员访问),则行为是未定义的
struct V { virtual void f(); virtual void g(); }; struct A : virtual V { virtual void f(); // A::f is the final overrider of V::f in A }; struct B : virtual V { virtual void g(); // B::g is the final overrider of V::g in B B(V*, A*); }; struct D : A, B { virtual void f(); // D::f is the final overrider of V::f in D virtual void g(); // D::g is the final overrider of V::g in D // note: A is initialized before B D() : B((A*) this, this) {} }; // the constructor of B, called from the constructor of D B::B(V* v, A* a) { f(); // virtual call to V::f (although D has the final overrider, D doesn't exist) g(); // virtual call to B::g, which is the final overrider in B v->g(); // v's type V is base of B, virtual call calls B::g as before a->f(); // a’s type A is not a base of B. it belongs to a different branch of the // hierarchy. Attempting a virtual call through that branch causes // undefined behavior even though A was already fully constructed in this // case (it was constructed before B since it appears before B in the list // of the bases of D). In practice, the virtual call to A::f will be // attempted using B's virtual member function table, since that's what // is active during B's construction) }
[edit] 关键字
[edit] 缺陷报告
以下行为更改的缺陷报告被追溯应用于先前发布的 C++ 标准。
DR | 应用于 | 已发布的行为 | 正确行为 |
---|---|---|---|
CWG 258 | C++98 | 派生类中的非 const 成员函数可能会因为其基类的 const 虚成员函数而变为 虚函数 |
虚函数也要求 cv- 限定符相同 |
CWG 477 | C++98 | 友元声明可以包含 virtual 说明符 | 不允许 |
CWG 1516 | C++98 | "虚函数调用" 和 "虚调用" 的定义未提供 |
已提供 |
[edit] 另请参阅
派生类和继承模式 | |
override 说明符(自 C++11 起) |
明确声明方法覆盖了另一个方法 |
final 说明符(自 C++11 起) |
声明方法不可被覆盖 |