虚拟成员函数
成员函数也可以声明为 virtual
。 在这种情况下,如果调用指针或对实例的引用,则不会直接访问它们; 相反,他们将查找虚函数表中的函数(虚函数的指向成员函数的列表,通常称为 vtable
或 vftable
),并使用它来调用适合实例动态的版本(实际)类型。如果直接从类的变量调用函数,则不执行查找。
struct Base {
virtual void func() { std::cout << "In Base." << std::endl; }
};
struct Derived : Base {
void func() override { std::cout << "In Derived." << std::endl; }
};
void slicer(Base x) { x.func(); }
// ...
Base b;
Derived d;
Base *pb = &b, *pd = &d; // Pointers.
Base &rb = b, &rd = d; // References.
b.func(); // Output: In Base.
d.func(); // Output: In Derived.
pb->func(); // Output: In Base.
pd->func(); // Output: In Derived.
rb.func(); // Output: In Base.
rd.func(); // Output: In Derived.
slicer(b); // Output: In Base.
slicer(d); // Output: In Base.
请注意,虽然 pd
是 Base*
,而 rd
是 Base&
,但是在两个叫 Derived::func()
而不是 Base::func()
中的任何一个上调用 func()
; 这是因为 Derived
的 vtable
更新了 Base::func()
条目,而不是指向 Derived::func()
。相反,请注意如何将实例传递给 slicer()
总是会导致 Base::func()
被调用,即使传递的实例是 Derived
; 这是因为有一些称为数据切片的东西,其中通过值将 Derived
实例传递到 Base
参数会使得 Derived
实例中不属于 Base
实例的部分无法访问。
当成员函数定义为 virtual 时,所有具有相同签名的派生类成员函数都会覆盖它,无论覆盖函数是否指定为 virtual
。然而,这可能使得派生类更难以被程序员解析,因为没有关于哪些函数是什么的指示 23。
struct B {
virtual void f() {}
};
struct D : B {
void f() {} // Implicitly virtual, overrides B::f.
// You'd have to check B to know that, though.
};
但是请注意,派生函数只有在其签名匹配时才会覆盖基函数; 即使派生函数明确声明为 virtual
,如果签名不匹配,它也会创建一个新的虚函数。
struct BadB {
virtual void f() {}
};
struct BadD : BadB {
virtual void f(int i) {} // Does NOT override BadB::f.
};
Version >= C++ 11
从 C++ 11 开始,可以使用上下文相关关键字 override
明确覆盖意图。这告诉编译器程序员希望它覆盖基类函数,这会导致编译器在没有覆盖任何内容时省略错误。
struct CPP11B {
virtual void f() {}
};
struct CPP11D : CPP11B {
void f() override {}
void f(int i) override {} // Error: Doesn't actually override anything.
};
这也有利于告诉程序员该函数既是虚拟的,又在至少一个基类中声明,这可以使复杂的类更容易解析。
当一个函数声明为 virtual
,并在类定义之外定义时,virtual
说明符必须包含在函数声明中,而不是在定义中重复。
Version >= C++ 11
这也适用于 override
。
struct VB {
virtual void f(); // "virtual" goes here.
void g();
};
/* virtual */ void VB::f() {} // Not here.
virtual void VB::g() {} // Error.
如果基类重载 virtual
函数,则只有明确指定为 virtual
的重载才是虚拟的。
struct BOverload {
virtual void func() {}
void func(int) {}
};
struct DOverload : BOverload {
void func() override {}
void func(int) {}
};
// ...
BOverload* bo = new DOverload;
bo->func(); // Calls DOverload::func().
bo->func(1); // Calls BOverload::func(int).
有关更多信息,请参阅相关主题 。