Namespaces
Variants

virtual function specifier

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Virtual function
override specifier (C++11)
final specifier (C++11)
Special member functions
Templates
Miscellaneous

指定某个非静态 成员函数 虚函数 并支持动态派发。该说明符仅可出现在非静态成员函数首次声明的 声明说明符序列 中(即在类定义内声明时)。

目录

说明

虚函数是那些行为可以在派生类中被重写的成员函数。与非虚函数不同,即使没有关于类实际类型的编译时信息,这种重写行为仍然保留。也就是说,如果通过基类的指针或引用处理派生类,调用被重写的虚函数将会调用派生类中定义的行为。这样的函数调用被称为 虚函数调用 虚调用 。如果使用 限定名称查找 选择函数(即函数名出现在作用域解析运算符 :: 右侧),则虚函数调用会被抑制。

#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)
}

若函数声明使用 override 说明符,但未覆写任何虚函数,则程序非良构:

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)
};

若函数声明使用 final 说明符,而其他函数试图覆写它,则程序非良构:

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; // 错误:受约束的虚函数
};

consteval 虚函数不得被非 consteval 虚函数重写,也不得重写非 consteval 虚函数。

(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 的构造期间活跃的虚表)
}

关键词

virtual

缺陷报告

以下行为变更缺陷报告被追溯应用于先前发布的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) 声明某方法不可被重写或某类不可被继承