要了解C++,您需要了解 C++ 的实现不是 C++ 的定义。
C++ 由抽象机器的行为定义。该抽象机的行为由标准定义,兼容的 C++ 编译器必须编译程序才能像在该抽象机上运行一样运行。
该抽象机器所做的事情的规则是基于真实计算机和 C++ 和 C 程序的真实实现的启发和基础。
因此,当您谈论“虚拟函数表”时,您谈论的是 C++ 对虚拟方法所做的一种常见实现。这种常见的实现没有定义 C++ 的行为方式,将两者混合起来可能会导致问题。
话虽这么说,C++ 的虚拟方法基于在 C 中做基本完全相同的事情。如果重新实现它,它可以帮助勾勒出虚拟方法和继承在 C++ 中的工作方式。(这有实际用途,因为这样做可以让您制作自定义对象模型,而自定义对象模型让您可以比 C++ 对象模型更有效地完成某些事情)。
struct Bob_vtable {
void(*print)(Bob const*) = 0;
};
struct Bob {
Bob_vtable const* vtable = 0;
int x = 0;
// glue code to dispatch to vtable:
void print() const {
return vtable->print(this);
}
// implementation of Bob::print:
static void print_impl( Bob const* self ) {
std::cout << self->x;
}
// vtable helpers:
static Bob_vtable make_vtable() {
return { &Bob::print_impl };
}
static Bob_vtable const* get_vtable() {
static const Bob_vtable retval = make_vtable();
return &retval;
}
Bob():vtable(get_vtable()) {}
};
这是一个非常简单的、没有继承的、Bob
具有单个虚拟方法的类的实现print
。大致对应:
class Bob {
public:
int x = 0;
virtual void print() const { std::cout << x; }
};
你会明白为什么为你编写所有这些胶水代码是件好事。
当你这样做时:
class Alice : public Bob {
public:
int y = 0;
void print() const override { std::cout << x << "," << y; }
};
“手动实施”看起来像:
struct Alice : Bob {
int y = 0;
// no print glue code needed(!)
// implementation of Alice::print:
static void print_impl( Bob const* bobself ) {
Alice const* self = static_cast<Alice const*>(bobself);
std::cout << self->x << "," << self->y;
}
static Bob_vtable make_vtable() {
Bob_vtable bob_version = Bob::make_vtable();
bob_version.print = &Alice::print_impl;
return bob_version;
}
static Bob_vtable const* get_vtable() {
static const Bob_vtable retval = make_vtable();
return &retval;
}
Alice():Bob() {
// after constructing Bob, replace the vtable with ours:
vtable = get_vtable();
}
};
你有它。
看看这里发生了什么:
Alice a;
a.print(std::cout);
现在,a.print
实际上调用Bob::print
,因为Alice
没有print
方法。
Bob.print
做这个:
void print() const {
return vtable->print(this);
}
它获取该对象实例的 vtable 指针,并在其中调用 print 函数。
什么是类型对象的 vtable 指针Alice
?看Alice
构造函数。
首先它是默认构造Bob
(设置vtable
为指向Bob
的 vtable),然后它会这样做:
vtable = get_vtable();
此调用get_vtable
调用Alice::get_vtable
:
static const Bob_vtable retval = make_vtable();
return &retval;
依次调用Alice::make_vtable
:
Bob_vtable bob_version = Bob::make_vtable();
bob_version.print = &Alice::print_impl;
return bob_version;
首先调用Bob
's make_vtable
,然后替换.print
为Alice::print_impl
.
所以Bob::print
调用vtable->print(this)
, 即Alice::print_impl(this)
, 执行以下操作:
Alice const* self = static_cast<Alice const*>(bobself);
std::cout << self->x << "," << self->y;
虽然此时this
是 a Bob const*
,它指向一个Alice
对象,所以这static_cast
是有效的。
所以我们打印x
和y
从Alice
.
现在,这里Alice
的 vtable 类型是Bob_vtable
因为她没有添加任何新方法。如果她添加新方法,她将拥有一个Alice_vtable
that 继承自Bob_vtable
,并且必须static_cast<Alice_vtable const*>(vtable)
访问它们。
这并不完全是C++ “在幕后”所做的,但它在逻辑上与我可以“即兴发挥”所写的一样。有很多不同的细节,比如vtable中函数的调用约定不同,内存中vtable的格式不匹配等等。
现在,在“手动实现”中,我确实使用了继承。所以那不是C;但是“手动实现”中的继承并没有做任何面向对象的事情。
struct A {int x;};
struct B:A{int y;};
只是在做
struct A {
int x;
};
struct B {
A base;
int y;
};
顶部有一点句法闪光。
“手动实施”与您将如何在c中实施(以及人们这样做)几乎是 1:1 的。您会将方法移出类,调用它们void Bob_print(Bob const*)
而不是void Bob::print() const
. 你会使用struct Alice { Bob base; int y; }
而不是struct Alice:Bob{ int y; };
. 但区别几乎完全是语法,而不是其他任何东西。
最初开发c++时,存在基于 OO 的 C,而 C++ 的首要目标之一是能够编写带有类的 C,而无需编写上述所有样板。
现在,C++ 的对象模型不需要上述实现。事实上,依赖上述实现可能会导致程序格式错误或未定义的行为。但是理解实现 C++ 对象模型的一种可能方式有一些用处。另外,一旦你知道如何实现 C++ 的对象模型,你就可以在 C++ 中使用不同的对象模型。
请注意,在现代 C++ 中,我会在上面使用更多模板来删除一些样板。作为实际用途,我使用了类似的技术来std::any
使用鸭子类型的虚拟方法来实现增强的 's。
结果是您可以获得以下语法:
auto print = poly_method<void(Self const*, std::ostream&)>{
[](auto const*self, std::ostream& os){ os << *self; }
};
poly_any<&print> x = 7;
x->*print(std::cout);
(不要在家里尝试这个)。