关于c ++:如何实现虚函数和vtable?

关于c ++:如何实现虚函数和vtable?

How are virtual functions and vtable implemented?

我们都知道C ++中的虚函数是什么,但它们是如何在深层次实现的?

可以在运行时修改甚至直接访问vtable吗?

vtable是否适用于所有类,或仅适用于至少具有一个虚函数的类?

抽象类对于至少一个条目的函数指针只有一个NULL吗?

有一个虚拟函数会减慢整个班级的速度吗? 或者只调用虚拟函数? 如果虚拟功能实际被覆盖了,速度是否会受到影响,或者只要它是虚拟的,它就没有效果。


虚拟功能如何在深层次实施?

从"C ++中的虚函数":

Whenever a program has a virtual function declared, a v - table is constructed for the class. The v-table consists of addresses to the virtual functions for classes that contain one or more virtual functions. The object of the class containing the virtual function contains a virtual pointer that points to the base address of the virtual table in memory. Whenever there is a virtual function call, the v-table is used to resolve to the function address. An object of the class that contains one or more virtual functions contains a virtual pointer called the vptr at the very beginning of the object in the memory. Hence the size of the object in this case increases by the size of the pointer. This vptr contains the base address of the virtual table in memory. Note that virtual tables are class specific, i.e., there is only one virtual table for a class irrespective of the number of virtual functions it contains. This virtual table in turn contains the base addresses of one or more virtual functions of the class. At the time when a virtual function is called on an object, the vptr of that object provides the base address of the virtual table for that class in memory. This table is used to resolve the function call as it contains the addresses of all the virtual functions of that class. This is how dynamic binding is resolved during a virtual function call.

可以在运行时修改甚至直接访问vtable吗?

一般来说,我认为答案是"不"。你可以做一些内存修改来找到vtable,但你仍然不知道函数签名是什么样的。如果没有直接访问vtable或在运行时修改它,应该可以使用此功能(语言支持)实现的任何功能。另请注意,C ++语言规范没有指定需要vtable - 但这就是大多数编译器实现虚函数的方式。

vtable是否适用于所有对象,或仅存在至少具有一个虚函数的对象?

我相信这里的答案是"它取决于实现",因为规范首先不需要vtable。但是,在实践中,我相信如果一个类至少有一个虚函数,所有现代编译器都只创建一个vtable。存在与vtable相关联的空间开销以及与调用虚拟功能与非虚拟功能相关联的时间开销。

抽象类对于至少一个条目的函数指针只有一个NULL吗?

答案是它没有通过语言规范指定,因此它取决于实现。如果未定义(通常不是),则调用纯虚函数会导致未定义的行为(ISO / IEC 14882:2003 10.4-2)。实际上,它确实在vtable中为函数分配了一个槽,但是没有为它分配地址。这使得vtable不完整,这需要派生类来实现该函数并完成vtable。有些实现只是在vtable条目中放置一个NULL指针;其他实现将指针指向一个虚拟方法,该方法执行类似于断言的操作。

请注意,抽象类可以定义纯虚函数的实现,但该函数只能使用qualified-id语法调用(即,在方法名称中完全指定类,类似于从中调用基类方法)派生类)。这样做是为了提供易于使用的默认实现,同时仍然要求派生类提供覆盖。

有一个虚函数会减慢整个类的速度,还是只调用虚函数?

这是我的知识的边缘,所以如果我错了,有人请帮助我!

我相信只有类中虚拟的函数才会遇到与调用虚函数和非虚函数相关的时间性能。这个类的空间开销是两种方式。请注意,如果存在vtable,则每个类只有1个,而不是每个对象一个。

如果虚拟功能实际被覆盖,速度是否会受到影响,或者只要它是虚拟的,它是否会起作用?

我不相信,与调用基本虚函数相比,被覆盖的虚函数的执行时间会减少。但是,与为派生类和基类定义另一个vtable相关联的类还有一个额外的空间开销。

其他资源:

http://www.codersource.net/published/view/325/virtual_functions_in.aspx(通过后机)
http://en.wikipedia.org/wiki/Virtual_table
http://www.codesourcery.com/public/cxx-abi/abi.html#vtable


  • 可以在运行时修改甚至直接访问vtable吗?

不便携,但如果你不介意肮脏的技巧,当然!

WARNING: This technique is not recommended for use by children, adults under the age of 969, or small furry creatures from Alpha Centauri. Side effects may include demons which fly out of your nose, the abrupt appearence of Yog-Sothoth as a required approver on all subsequent code reviews, or the retroactive addition of IHuman::PlayPiano() to all existing instances]

在我看过的大多数编译器中,vtbl *是对象的前4个字节,而vtbl内容只是一个成员指针数组(通常按照它们被声明的顺序,基类是第一个)。当然还有其他可能的布局,但这是我一般观察到的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A {
  public:
  virtual int f1() = 0;
};
class B : public A {
  public:
  virtual int f1() { return 1; }
  virtual int f2() { return 2; }
};
class C : public A {
  public:
  virtual int f1() { return -1; }
  virtual int f2() { return -2; }
};

A *x = new B;
A *y = new C;
A *z = new C;

现在要拉一些恶作剧......

在运行时更改类:

1
2
std::swap(*(void **)x, *(void **)y);
// Now x is a C, and y is a B! Hope they used the same layout of members!

替换所有实例的方法(monkeypatching class)

这个有点棘手,因为vtbl本身可能只在只读内存中。

1
2
3
4
5
6
7
int f3(A*) { return 0; }

mprotect(*(void **)x,8,PROT_READ|PROT_WRITE|PROT_EXEC);
// Or VirtualProtect on win32; this part's very OS-specific
(*(int (***)(A *)x)[0] = f3;
// Now C::f1() returns 0 (remember we made x into a C above)
// so x->f1() and z->f1() both return 0

由于mprotect操作,后者很可能使病毒检查程序和链接唤醒并注意到。在使用NX位的过程中,它可能会失败。


有一个虚拟函数会减慢整个班级的速度吗?

Or only the call to the function that is virtual? And does the speed get affected if the virtual function is actually overwritten or not, or does this have no effect so long as it is virtual.

只要在处理这样一个类的对象时,必须初始化,复制另一个数据项,虚函数就会减慢整个类的速度。对于有六个左右成员的班级,差异应该是可以忽略的。对于只包含一个char成员的类,或者根本没有成员的类,差异可能是值得注意的。

除此之外,重要的是要注意,并非每次调用虚函数都是虚函数调用。如果你有一个已知类型的对象,编译器可以为正常的函数调用发出代码,甚至可以在感觉就好的情况下内联所述函数。只有当您通过可能指向基类的对象或某个派生类的对象的指针或引用进行多态调用时,您才需要vtable间接并根据性能付费。

1
2
3
4
5
6
7
8
9
struct Foo { virtual ~Foo(); virtual int a() { return 1; } };
struct Bar: public Foo { int a() { return 2; } };
void f(Foo& arg) {
  Foo x; x.a(); // non-virtual: always calls Foo::a()
  Bar y; y.a(); // non-virtual: always calls Bar::a()
  arg.a();      // virtual: must dispatch via vtable
  Foo z = arg;  // copy constructor Foo::Foo(const Foo&) will convert to Foo
  z.a();        // non-virtual Foo::a, since z is a Foo, even if arg was not
}

无论函数是否被覆盖,硬件必须采取的步骤基本相同。从对象读取vtable的地址,从适当的槽中检索函数指针,以及由指针调用的函数。就实际绩效而言,分支预测可能会产生一些影响。因此,例如,如果您的大多数对象引用给定虚函数的相同实现,那么即使在检索指针之前,分支预测器也有可能正确地预测要调用的函数。但是哪个函数是常见函数无关紧要:它可能是委托给非重写基本案例的大多数对象,或属于同一子类的大多数对象,因此委托给同一个被覆盖的案例。

他们是如何在深层次实施的?

我喜欢jheriko的想法,使用模拟实现来证明这一点。但是我使用C来实现类似于上面代码的东西,因此更容易看到低级别。

父类Foo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef struct Foo_t Foo;   // forward declaration
struct slotsFoo {           // list all virtual functions of Foo
  const void *parentVtable; // (single) inheritance
  void (*destructor)(Foo*); // virtual destructor Foo::~Foo
  int (*a)(Foo*);           // virtual function Foo::a
};
struct Foo_t {                      // class Foo
  const struct slotsFoo* vtable;    // each instance points to vtable
};
void destructFoo(Foo* self) { }     // Foo::~Foo
int aFoo(Foo* self) { return 1; }   // Foo::a()
const struct slotsFoo vtableFoo = { // only one constant table
  0,                                // no parent class
  destructFoo,
  aFoo
};
void constructFoo(Foo* self) {      // Foo::Foo()
  self->vtable = &vtableFoo;        // object points to class vtable
}
void copyConstructFoo(Foo* self,
                      Foo* other) { // Foo::Foo(const Foo&)
  self->vtable = &vtableFoo;        // don't copy from other!
}

派生类吧

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct Bar_t {              // class Bar
  Foo base;                         // inherit all members of Foo
} Bar;
void destructBar(Bar* self) { }     // Bar::~Bar
int aBar(Bar* self) { return 2; }   // Bar::a()
const struct slotsFoo vtableBar = { // one more constant table
  &vtableFoo,                       // can dynamic_cast to Foo
  (void(*)(Foo*)) destructBar,      // must cast type to avoid errors
  (int(*)(Foo*)) aBar
};
void constructBar(Bar* self) {      // Bar::Bar()
  self->base.vtable = &vtableBar;   // point to Bar vtable
}

函数f执行虚函数调用

1
2
3
4
5
6
7
8
9
10
void f(Foo* arg) {                  // same functionality as above
  Foo x; constructFoo(&x); aFoo(&x);
  Bar y; constructBar(&y); aBar(&y);
  arg->vtable->a(arg);              // virtual function call
  Foo z; copyConstructFoo(&z, arg);
  aFoo(&z);
  destructFoo(&z);
  destructBar(&y);
  destructFoo(&x);
}

所以你可以看到,vtable只是内存中的一个静态块,主要包含函数指针。多态类的每个对象都将指向与其动态类型对应的vtable。这也使得RTTI和虚函数之间的连接更加清晰:您可以通过查看它所指向的vtable来检查类的类型。以上是以许多方式简化的,例如,多重继承,但一般概念是健全的。

如果arg的类型为Foo*并且您使用arg->vtable,但实际上是Bar类型的对象,那么您仍然可以获得vtable的正确地址。那是因为vtable始终是对象地址的第一个元素,无论它是在正确类型的表达式中被称为vtable还是base.vtable


这是现代C ++中虚拟表的可运行手动实现。 它有明确定义的语义,没有黑客,也没有void*

注意:.*->*是与*->不同的运算符。 成员函数指针的工作方式不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include <iostream>
#include <vector>
#include <memory>

struct vtable; // forward declare, we need just name

class animal
{
public:
    const std::string& get_name() const { return name; }

    // these will be abstract
    bool has_tail() const;
    bool has_wings() const;
    void sound() const;

protected: // we do not want animals to be created directly
    animal(const vtable* vtable_ptr, std::string name)
    : vtable_ptr(vtable_ptr), name(std::move(name)) { }

private:
    friend vtable; // just in case for non-public methods

    const vtable* const vtable_ptr;
    std::string name;
};

class cat : public animal
{
public:
    cat(std::string name);

    // functions to bind dynamically
    bool has_tail() const { return true; }
    bool has_wings() const { return false; }
    void sound() const
    {
        std::cout << get_name() <<" does meow
"
;
    }
};

class dog : public animal
{
public:
    dog(std::string name);

    // functions to bind dynamically
    bool has_tail() const { return true; }
    bool has_wings() const { return false; }
    void sound() const
    {
        std::cout << get_name() <<" does whoof
"
;
    }
};

class parrot : public animal
{
public:
    parrot(std::string name);

    // functions to bind dynamically
    bool has_tail() const { return false; }
    bool has_wings() const { return true; }
    void sound() const
    {
        std::cout << get_name() <<" does crrra
"
;
    }
};

// now the magic - pointers to member functions!
struct vtable
{
    bool (animal::* const has_tail)() const;
    bool (animal::* const has_wings)() const;
    void (animal::* const sound)() const;

    // constructor
    vtable (
        bool (animal::* const has_tail)() const,
        bool (animal::* const has_wings)() const,
        void (animal::* const sound)() const
    ) : has_tail(has_tail), has_wings(has_wings), sound(sound) { }
};

// global vtable objects
const vtable vtable_cat(
    static_cast<bool (animal::*)() const>(&cat::has_tail),
    static_cast<bool (animal::*)() const>(&cat::has_wings),
    static_cast<void (animal::*)() const>(&cat::sound));
const vtable vtable_dog(
    static_cast<bool (animal::*)() const>(&dog::has_tail),
    static_cast<bool (animal::*)() const>(&dog::has_wings),
    static_cast<void (animal::*)() const>(&dog::sound));
const vtable vtable_parrot(
    static_cast<bool (animal::*)() const>(&parrot::has_tail),
    static_cast<bool (animal::*)() const>(&parrot::has_wings),
    static_cast<void (animal::*)() const>(&parrot::sound));

// set vtable pointers in constructors
cat::cat(std::string name) : animal(&vtable_cat, std::move(name)) { }
dog::dog(std::string name) : animal(&vtable_dog, std::move(name)) { }
parrot::parrot(std::string name) : animal(&vtable_parrot, std::move(name)) { }

// implement dynamic dispatch
bool animal::has_tail() const
{
    return (this->*(vtable_ptr->has_tail))();
}

bool animal::has_wings() const
{
    return (this->*(vtable_ptr->has_wings))();
}

void animal::sound() const
{
    (this->*(vtable_ptr->sound))();
}

int main()
{
    std::vector<std::unique_ptr> animals;
    animals.push_back(std::make_unique<cat>("grumpy"));
    animals.push_back(std::make_unique<cat>("nyan"));
    animals.push_back(std::make_unique<dog>("doge"));
    animals.push_back(std::make_unique<parrot>("party"));

    for (const auto& a : animals)
        a->sound();

    // note: destructors are not dispatched virtually
}

我会试着让它变得简单:)

我们都知道C ++中的虚函数是什么,但它们是如何在深层次实现的?

这是一个带有指向函数的指针的数组,这些函数是特定虚函数的实现。此数组中的索引表示为类定义的虚函数的特定索引。这包括纯虚函数。

当多态类派生自另一个多态类时,我们可能会遇到以下情况:

  • 派生类不会添加新的虚函数,也不会覆盖任何虚函数。在这种情况下,此类与基类共享vtable。
  • 派生类添加并覆盖虚拟方法。在这种情况下,它获得自己的vtable,其中添加的虚函数的索引从最后一个派生开始。
  • 继承中有多个多态类。在这种情况下,我们在第二个和下一个基数之间有一个索引转换,并在派生类中有它的索引

可以在运行时修改甚至直接访问vtable吗?

不标准的方式 - 没有API可以访问它们。编译器可能有一些扩展或私有API来访问它们,但这可能只是一个扩展。

vtable是否适用于所有类,或仅适用于至少具有一个虚函数的类?

只有那些至少有一个虚函数(甚至是析构函数)或派生至少一个具有vtable的类("是多态的")。

抽象类对于至少一个条目的函数指针只有一个NULL吗?

这是一种可能的实施方式,但并未实施。相反,通常有一个函数可以打印类似"纯虚函数调用"和abort()的函数。如果您尝试在构造函数或析构函数中调用抽象方法,则可能会发生对此的调用。

有一个虚拟函数会减慢整个班级的速度吗?或者只调用虚拟函数?如果虚拟功能实际被覆盖了,速度是否会受到影响,或者只要它是虚拟的,它就没有效果。

减速仅取决于呼叫是作为直接呼叫还是作为虚拟呼叫解决。没有其他事情重要。 :)

如果通过指针或对象的引用来调用虚函数,那么它将始终实现为虚拟调用 - 因为编译器永远不会知道在运行时将为此指针分配哪种对象,以及它是否为是否重写此方法的类。只有在两种情况下,编译器才能将对虚拟函数的调用解析为直接调用:

  • 如果通过值(变量或返回值的函数的结果)调用方法 - 在这种情况下,编译器不会怀疑对象的实际类是什么,并且可以在编译时"硬解析"它。
  • 如果虚拟方法在您有一个指针或引用的类中声明为final,则通过该方法调用它(仅在C ++ 11中)。在这种情况下,编译器知道此方法不能进行任何进一步的覆盖,它只能是此类中的方法。

请注意,虚拟调用只有解除引用两个指针的开销。使用RTTI(尽管只适用于多态类)比调用虚方法慢,如果你找到一个案例来实现同样的两种方式。例如,定义virtual bool HasHoof() { return false; }然后仅覆盖bool Horse::HasHoof() { return true; }将使您能够调用if (anim->HasHoof()),这比尝试if(dynamic_cast(anim))更快。这是因为dynamic_cast在某些情况下甚至必须递归地遍历类层次结构,以查看是否可以从实际指针类型和所需的类类型构建路径。虽然虚拟调用始终是相同的 - 取消引用两个指针。


您可以使用函数指针作为类的成员和静态函数作为实现,或使用指向成员函数的指针和实现的成员函数,在C ++中重新创建虚函数的功能。这两种方法之间只有符号优势......事实上,虚函数调用本身只是一种符号方便。事实上,继承只是一种符号方便性......它可以在不使用语言特性进行继承的情况下实现。 :)

下面是未经测试的废话,可能是错误的代码,但希望能够证明这个想法。

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Foo
{
protected:
 void(*)(Foo*) MyFunc;
public:
 Foo() { MyFunc = 0; }
 void ReplciatedVirtualFunctionCall()
 {
  MyFunc(*this);
 }
...
};

class Bar : public Foo
{
private:
 static void impl1(Foo* f)
 {
  ...
 }
public:
 Bar() { MyFunc = impl1; }
...
};

class Baz : public Foo
{
private:
 static void impl2(Foo* f)
 {
  ...
 }
public:
 Baz() { MyFunc = impl2; }
...
};


这个答案已被纳入社区维基答案

  • 抽象类对于至少一个条目的函数指针只有一个NULL吗?

答案是它未指定 - 调用纯虚函数会导致未定义的行为(如果未定义)(通常不是)(ISO / IEC 14882:2003 10.4-2)。有些实现只是在vtable条目中放置一个NULL指针;其他实现将指针指向一个虚拟方法,该方法执行类似于断言的操作。

请注意,抽象类可以定义纯虚函数的实现,但该函数只能使用qualified-id语法调用(即,在方法名称中完全指定类,类似于从中调用基类方法)派生类)。这样做是为了提供易于使用的默认实现,同时仍然要求派生类提供覆盖。


通常使用VTable,指向函数的指针数组。


所有这些答案中没有提到的是在多重继承的情况下,基类都有虚拟方法。继承类有多个指向vmt的指针。
结果是这样一个对象的每个实例的大小更大。
每个人都知道具有虚方法的类对于vmt有4个字节的额外内容,但是在多重继承的情况下,对于每个基类,其具有虚拟方法的时间为4. 4是指针的大小。


每个对象都有一个vtable指针,指向一个成员函数数组。


非常可爱的概念证明我提前做了一点(看看遗产的顺序是否重要); 让我知道你的C ++实现是否真的拒绝它(我的gcc版本只给出了分配匿名结构的警告,但那是一个bug),我很好奇。

CCPolite.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef CCPOLITE_H
#define CCPOLITE_H

/* the vtable or interface */
typedef struct {
    void (*Greet)(void *);
    void (*Thank)(void *);
} ICCPolite;

/**
 * the actual"object" literal as C++ sees it; public variables be here too
 * all CPolite objects use(are instances of) this struct's structure.
 */

typedef struct {
    ICCPolite *vtbl;
} CPolite;

#endif /* CCPOLITE_H */

CCPolite_constructor.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * unconventionally include me after defining OBJECT_NAME to automate
 * static(allocation-less) construction.
 *
 * note: I assume CPOLITE_H is included; since if I use anonymous structs
 *     for each object, they become incompatible and cause compile time errors
 *     when trying to do stuff like assign, or pass functions.
 *     this is similar to how you can't pass void * to windows functions that
 *         take handles; these handles use anonymous structs to make
 *         HWND/HANDLE/HINSTANCE/void*/
etc not automatically convertible, and
 *         require a cast.
 */
#ifndef OBJECT_NAME
    #error CCPolite> constructor requires object name.
#endif

CPolite OBJECT_NAME = {
    &CCPolite_Vtbl
};

/* ensure no global scope pollution */
#undef OBJECT_NAME

main.c中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <stdio.h>
#include"CCPolite.h"

// | A Greeter is capable of greeting; nothing else.
struct IGreeter
{
    virtual void Greet() = 0;
};

// | A Thanker is capable of thanking; nothing else.
struct IThanker
{
    virtual void Thank() = 0;
};

// | A Polite is something that implements both IGreeter and IThanker
// | Note that order of implementation DOES MATTER.
struct IPolite1 : public IGreeter, public IThanker{};
struct IPolite2 : public IThanker, public IGreeter{};

// | implementation if IPolite1; implements IGreeter BEFORE IThanker
struct CPolite1 : public IPolite1
{
    void Greet()
    {
        puts("hello!");
    }

    void Thank()
    {
        puts("thank you!");
    }
};

// | implementation if IPolite1; implements IThanker BEFORE IGreeter
struct CPolite2 : public IPolite2
{
    void Greet()
    {
        puts("hi!");
    }

    void Thank()
    {
        puts("ty!");
    }
};

// | imposter Polite's Greet implementation.
static void CCPolite_Greet(void *)
{
    puts("HI I AM C!!!!");
}

// | imposter Polite's Thank implementation.
static void CCPolite_Thank(void *)
{
    puts("THANK YOU, I AM C!!");
}

// | vtable of the imposter Polite.
ICCPolite CCPolite_Vtbl = {
    CCPolite_Thank,
    CCPolite_Greet    
};

CPolite CCPoliteObj = {
    &CCPolite_Vtbl
};

int main(int argc, char **argv)
{
    puts("
part 1"
);
    CPolite1 o1;
    o1.Greet();
    o1.Thank();

    puts("
part 2"
);    
    CPolite2 o2;    
    o2.Greet();
    o2.Thank();    

    puts("
part 3"
);    
    CPolite1 *not1 = (CPolite1 *)&o2;
    CPolite2 *not2 = (CPolite2 *)&o1;
    not1->Greet();
    not1->Thank();
    not2->Greet();
    not2->Thank();

    puts("
part 4"
);        
    CPolite1 *fake = (CPolite1 *)&CCPoliteObj;
    fake->Thank();
    fake->Greet();

    puts("
part 5"
);        
    CPolite2 *fake2 = (CPolite2 *)fake;
    fake2->Thank();
    fake2->Greet();

    puts("
part 6"
);        
    #define OBJECT_NAME fake3
    #include"CCPolite_constructor.h"
    fake = (CPolite1 *)&fake3;
    fake->Thank();
    fake->Greet();

    puts("
part 7"
);        
    #define OBJECT_NAME fake4
    #include"CCPolite_constructor.h"
    fake2 = (CPolite2 *)&fake4;
    fake2->Thank();
    fake2->Greet();    

    return 0;
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
part 1
hello!
thank you!

part 2
hi!
ty!

part 3
ty!
hi!
thank you!
hello!

part 4
HI I AM C!!!!
THANK YOU, I AM C!!

part 5
THANK YOU, I AM C!!
HI I AM C!!!!

part 6
HI I AM C!!!!
THANK YOU, I AM C!!

part 7
THANK YOU, I AM C!!
HI I AM C!!!!

请注意,因为我从不分配我的假物品,所以不需要做任何破坏; 析构函数自动放在动态分配对象范围的末尾,以回收对象文本本身和vtable指针的内存。


Burly的答案在这里是正确的,除了这个问题:

抽象类对于至少一个条目的函数指针只有一个NULL吗?

答案是没有为抽象类创建任何虚拟表。没有必要,因为不能创建这些类的对象!

换句话说,如果我们有:

1
2
3
4
5
class B { ~B() = 0; }; // Abstract Base class
class D : public B { ~D() {} }; // Concrete Derived class

D* pD = new D();
B* pB = pD;

通过pB访问的vtbl指针将是D类的vtbl。这正是多态性的实现方式。也就是说,如何通过pB访问D方法。 B类不需要vtbl。

回应Mike的评论如下......

如果我的描述中的B类有一个虚拟方法foo()没有被D覆盖,而虚拟方法bar()被覆盖,那么D的vtbl将有一个指向B的foo()和它自己的bar()的指针。仍然没有为B创建vtbl。


推荐阅读