命名空间
变体
操作

virtual 函数指定符

来自 cppreference.cn
< cpp‎ | language
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句(循环)
for
范围-for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 指定符
动态异常规范 在 C++17 前已弃用*
noexcept 指定符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储期说明符
初始化
 
 

virtual 说明符指定非静态成员函数虚函数,并支持动态分发。它只能出现在非静态成员函数的初始声明的声明符序列中(即,当在类定义中声明时)。

目录

[编辑] 解释

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

#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 限定符
  • ref 限定符

那么类 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)
}

如果一个函数使用说明符 override 声明,但没有重写虚函数,则程序是非良构的

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

如果一个函数使用说明符 final 声明,并且另一个函数尝试重写它,则程序是非良构的

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

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; // 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*
}

[编辑] 虚析构函数

即使析构函数不可继承,如果基类将其析构函数声明为 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 表达式时,任何基类的析构函数都必须是 public 且虚函数或 protected 且非虚函数,例如,当隐式用于 std::unique_ptr 中时(自 C++11 起)

[编辑] 在构造和析构期间

当从构造函数或析构函数直接或间接调用虚函数时(包括在类的非静态数据成员的构造或析构期间,例如在成员初始化列表中),并且调用应用到的对象是正在构造或析构的对象,则调用的函数是构造函数或析构函数的类中的最终覆盖者,而不是在更派生的类中重写它的函数。换句话说,在构造或析构期间,更派生的类不存在。

当构造具有多个分支的复杂类时,在属于一个分支的构造函数中,多态性被限制在该类及其基类:如果它获得指向此子层次结构外部的基类子对象的指针或引用,并尝试调用虚函数(例如,使用显式成员访问),则行为是未定义的

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

[编辑] 关键字

virtual

[编辑] 缺陷报告

以下行为变更缺陷报告被追溯应用于先前发布的 C++ 标准。

DR 应用于 已发布行为 正确行为
CWG 258 C++98 派生类的非 const 成员函数可能变为
虚函数,因为其基类的 const 虚成员函数
虚属性也需要 cv-
限定符相同
CWG 477 C++98 friend 声明可能包含 virtual 说明符 不允许
CWG 1516 C++98 术语“虚函数调用”的定义
和“虚调用”未提供
已提供

[编辑] 参见

派生类和继承模式
override 说明符 (C++11) 显式声明方法重写另一个方法[编辑]
final 说明符 (C++11) 声明方法不能被重写或类不能被派生[编辑]