多态
Virtual function
- The biggest benefit of virtual functions: the ability to structure your code in such a way that newly derived classes will automatically work with the old code without modification!
- The signature of the derived class function must exactly match the signature of the base class virtual function in order for the derived class function to be used. 为此引入了
override
关键字,意在检查稍有不慎将“虚函数重写(override)”写成了“函数重载(overload)” - Note that if a function is marked as virtual, all matching overrides are also considered virtual, even if they are not explicitly marked as such.
Never call virtual functions from constructors or destructors.
Remember that when a Derived class is created, the Base portion is constructed first. If you were to call a virtual function from the Base constructor, and Derived portion of the class hadn’t even been created yet, it would be unable to call the Derived version of the function because there’s no Derived object for the Derived function to work on. In C++, it will call the Base version instead.
A similar issue exists for destructors. If you call a virtual function in a Base class destructor, it will always resolve to the Base class version of the function, because the Derived portion of the class will already have been destroyed.
Pure virtual function
A pure virtual function is a function that must be overridden in a derived class and need not be defined. A virtual function is declared to be “pure” using the curious =0
syntax. For example:
class Base {
public:
void f1(); // not virtual
virtual void f2(); // virtual, not pure
virtual void f3() = 0; // pure virtual
};
Base b; // error: pure virtual f3 not overridden
Here, Base
is an abstract class (because it has a pure virtual function), so no objects of class Base
can be directly created: Base
is (explicitly) meant to be a base class. For example:
class Derived : public Base {
// no f1: fine
// no f2: fine, we inherit Base::f2
void f3();
};
Derived d; // ok: Derived::f3 overrides Base::f3
Abstract classes are immensely useful for defining interfaces. In fact, a class with no data and where all functions are pure virtual functions is often called an interface.
Base::f3()
must still be overridden in some derived class. If you don’t override a pure virtual function in a derived class, that derived class becomes abstract:
class D2 : public Base {
// no f1: fine
// no f2: fine, we inherit Base::f2
// no f3: fine, but D2 is therefore still abstract
};
D2 d; // error: pure virtual Base::f3 not overridden
包含纯虚函数的类称为虚基类(abstract class),不能实例化。
Dynamic binding
Dynamic binding means that the address of the code in a member function invocation is determined at the last possible moment: based on the dynamic type of the object at run time. It is called “dynamic binding” because the binding to the code that actually gets called is accomplished dynamically (at run time). Dynamic binding is a result of virtual
functions.
see alse 静态绑定
What’s the difference between how virtual
and non-virtual
member functions are called?
Non-virtual
member functions are resolved statically. That is, the member function is selected statically (at compile-time) based on the type of the pointer (or reference) to the object.
In contrast, virtual
member function are resolved dynamically (at run-time). That is, the member function is selected dynamically (at run-time) based on the type of the object, not the type of the pointer/reference to that object. This is called “dynamic binding”. Most compilers use some variant of the following technique: if the object has one or more virtual
functions, the compiler puts a hidden pointer in the object called a “virtual-pointer” or “v-pointer.” This v-pointer points to a global table called the “virtual-table” or “v-table.”
The compiler creates a v-table for each class that has at least one virtual
function. For example, if class Circle
has virtual
functions for draw()
and move()
and resize()
, there would be exactly one v-table associated with class Circle
, even if there were a gazillion Cricle
objects, and the v-pointer of each of those Circle
objects would point to the Circle
v-table. The v-table itself has pointers to each of the virtual functions in the class. For example, the Circle
v-table would have three pointers: a pointer to Circle::draw()
, a pointer to Circle::move()
, and a pointer to Circle::resize()
.
每个实例持有一个vptr,共享一个vtable。也就是说,含有虚函数的类只有一份vtable,它的所有实例均有一个vptr,指向这个vtable。Am I clear?
During a dispatch of a virtual
function, the run-time system follows the object’s v-pointer to the class’s v-table, then follows the appropriate slot in the v-table to the method code.
The space-cost overhead of the above technique is nominal: an extra pointer per object (but only for objects that will need to do dynamic binding), plus an extra pointer per method (but only for virtual methods). The time-cost overhead is also fairly nominal: compared to a normal function call, a virtual
function call requires two extra fetches (one to get the value of the v-pointer, a second to get the address of the method). None of this runtime activity happens with non-virtual
functions, since the compiler resolves non-virtual
functions exclusively at compile-time based on the type of theco pointer.
Note: the above discussion is simplified considerably, since it doesn’t account for extra structural things like multiple inheritance, virtual
inheritance, RTTI, etc., nor does it account for space/speed issues such as page faults, calling a function via a pointer-to-function, etc.
When should my destructor be virtual
?
virtual-destructor 虚析构函数 destructor
派生类析构函数会主动调用基类析构函数(内部机制),因此,当通过基类指针删除对象时,如果析构函数不是虚的,就调不到派生类析构函数,进而导致派生类的资源释放问题。
==When someone will delete
a derived-class object via a base-class pointer.==
In particular, here’s when you need to make your destructor virtual
:
- if someone will derive from your class,
- and if someone will say
new Derived
, whereDerived
is derived from your class, - and if someone will say
delete p
, where the actual object’s type isDerived
but the pointerp
’s type is your class.
Confused? Here’s a simplified rule of thumb that usually protects you and usually doesn’t cost you anything: make your destructor virtual
if your class has any virtual
functions. Rationale:
- that usually protects you because most base classes have at least one
virtual
function. - that usually doesn’t cost you anything because there is no added per-object space-cost for the second or subsequent
virtual
in your class. In other words, you’ve already paid all the per-object space-cost that you’ll ever pay once you add the firstvirtual
function, so thevirtual
destructor doesn’t add any additional per-object space cost. (Everything in this bullet is theoretically compiler-specific, but in practice it will be valid on almost all compilers.)
Note: in a derived class, if your base class has a virtual
destructor, your own destructor is automatically virtual
. You might need an explicitly defined destructor for other reasons, but there’s no need to redeclare a destructor simply to make sure it is virtual
. No matter whether you declare it with the virtual
keyword, declare it without the virtual
keyword, or don’t declare it at all, it’s still virtual
.
By the way, if you’re interested, here are the mechanical details of why you need a virtual
destructor when someone says delete
using a Base
pointer that’s pointing at a Derived
object. When you say delete p
, and the class of p
has a virtual
destructor, the destructor that gets invoked is the one associated with the type of the object *p
, not necessarily the one associated with the type of the pointer. This is A Good Thing. In fact, violating that rule makes your program undefined. The technical term for that is, “Yuck.”
Why are destructors not virtual
by default?
Because many classes are not designed to be used as base classes. Virtual functions make sense only in classes meant to act as interfaces to objects of derived classes (typically allocated on a heap and accessed through pointers or references).
So when should I declare a destructor virtual? Whenever the class has at least one virtual function. Having virtual functions indicate that a class is meant to act as an interface to derived classes, and when it is, an object of a derived class may be destroyed through a pointer to the base. For example:
class Base {
// ...
virtual ~Base();
};
class Derived : public Base {
// ...
~Derived();
};
void f()
{
Base* p = new Derived;
delete p; // virtual destructor used to ensure that ~Derived is called
}
Had Base
’s destructor not been virtual, Derived
’s destructor would not have been called – with likely bad effects, such as resources owned by Derived
not being freed.
Why don’t we have virtual
constructors?
A virtual call is a mechanism to get work done given partial information. In particular, virtual
allows us to call a function knowing only an interfaces and not the exact type of the object. To create an object you need complete information. In particular, you need to know the exact type of what you want to create. Consequently, a “call to a constructor” cannot be virtual.
see also ctor不能是虚函数 and 不要在ctor里调用虚函数.
For example:
#include <iostream>
using namespace std;
class Base
{
public:
Base() { Foo(); }
virtual void Foo() { cout << "Base::Foo\n"; }
};
class Derived : public Base
{
public:
Derived(): Base() {}
virtual void Foo() { cout << "Derived::Foo\n"; }
};
int main()
{
Derived* d = new Derived();
d->Foo();
return 0;
}
will outputs
Base::Foo
Derived::Foo
Note that, when we call the constructor of Derived
, no dynamic binding happened.
Never call virtual functions during construction or destruction
You shouldn’t call virtual functions during construction or destruction, because the calls won’t do what you think, and if they did, you’d still be unhappy. During base class construction, virtual functions never go down into derived classes.
Base class constructors execute before derived class constructors, derived class data members have not been initialized when base class constructors run. If virtual functions called during base class construction went down to derived classes, the derived class functions would almost certainly refer to local data members, but those data members would not yet have been initialized. That would be a non-stop ticket to undefined behavior.
It’s actually more fundamental than that. During base class construction of a derived class object, the type of the object is that of the base class. Not only do virtual functions resolve to the base class, but the parts of the language using runtime type information (e.g.,
dynamic_cast
(see Item 27) and typeid
) treat the object as a base class type. An object doesn’t become a derived class object until execution of a derived class constructor begins.
The same reasoning applies during destruction. Once a derived class destructor has run, the object’s derived class data members assume undefined values, so C++ treats them as if they no longer exist. Upon entry to the base class destructor, the object becomes a base class object, and all parts of C++ — virtual functions, dynamic_casts, etc., — treat it that way.
构造函数执行完,对象才算构造完成。如果要在对象构造中去动态绑定,我们知道它的原理其实是通过对象的vptr去访问vtable, 进而查询得到正确的调用地址。对象在构造中,如果调用到派生类的方法,而该方法又依赖于派生类新增的数据成员(此时还未被初始化),那么这次访问就不太合理。所以C++干脆就不让在构造函数里面调虚函数,调了也不会发生动态绑定。
同理,在析构函数中,如果调用到派生类的方法,而该方法又依赖派生类的数据成员,此时派生类的数据成员可能被销毁1,那么这次访问依旧是不合法的。
Things to Remember ✦ Don’t call virtual functions during construction or destruction, because such calls will never go to a more derived class than that of the currently executing constructor or destructor
References
- Inheritance —
virtual
functions - Effective C++, Scott Meyers, 3rd Edition
Footnotes
-
构造时,总是先构造基类成员,再构造派生类成员。析构时相反,先销毁派生类成员,再销毁基类成员。 ↩