要理解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指针,并在其中调用打印函数。
Alice 类型对象的 vtable 指针是什么?查看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的make_vtable,然后用Alice::print_impl替换.print。
所以Bob::print 调用vtable->print(this),这是Alice::print_impl(this),它确实:
Alice const* self = static_cast<Alice const*>(bobself);
std::cout << self->x << "," << self->y;
虽然此时this 是Bob const*,但它指向的是Alice 对象,因此static_cast 是有效的。
所以我们从Alice 打印x 和y。
现在,Alice 的 vtable 类型是 Bob_vtable,因为她没有添加任何新方法。如果她添加新方法,她将拥有一个继承自 Bob_vtable 的 Alice_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。
结果是你可以得到这个语法:
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);
(不要在家里尝试这个)。