这在Iswitch和MySwitch的class“compiler does it for me”版本中所做的事情与在手册struct版本中所做的事情基本相同。 dynamic_destroy是我添加的一个特殊的助手。当你通过一个虚拟析构函数指针删除一个C++对象时,它会在vtable中查找析构函数并使用它来清理对象。
Iswitch* pswitch = new MySwitch;
delete pswitch;
在手动struct的情况下,上述内容变为:
// Iswitch* pswitch = new MySwitch; translates to:
Iswitch* pswitch = ::new( malloc( sizeof(MySwitch) ) ) MySwitch{};
// (with an extra try-catch to clean up the malloc memory if the ctor throws)
// delete pswitch; translates to:
pswitch->dynamic_destroy();
free(pswitch);
// (usually dtors are nothrow, so no try-catch needed here)
在这里,您可以看到the two versions并行编译。 在真实的生成的代码中有一些差异。 1.我创建了helper函数,而真实的生成的代码内联了它们。
1条答案
按热度按时间ar7v8xwq1#
每个主要的C++实现都使用vtable。这是一个指向方法指针数组(或结构体)的指针。对于给定类型有一个vtable(不包括涉及动态加载的一些角落情况)。
如果你这样写:
编译器创建了类似于下面的内容:
在C++编译器生成的汇编级,一个封装函数指针数组和一个封装函数指针结构是相同的。
当你从
Iswitch
继承时:编译器生成类似于以下的输出:
当创建派生对象时,它将基对象中的vtable指针设置为指向其vtable,作为其构造函数的一部分。
然后,当您通过虚拟分派(通常只是调用它)访问方法时,代码在vtable中查找方法指针并调用它。
如果你有
无论
pswitch
指向Iswitch
的哪个派生类,(在此函数中)运行的代码都是相同的;至少直到它到达turn_on
或turn_off
的主体。这在
Iswitch
和MySwitch
的class
“compiler does it for me”版本中所做的事情与在手册struct
版本中所做的事情基本相同。dynamic_destroy
是我添加的一个特殊的助手。当你通过一个虚拟析构函数指针删除一个C++对象时,它会在vtable中查找析构函数并使用它来清理对象。在手动
struct
的情况下,上述内容变为:在这里,您可以看到the two versions并行编译。
在真实的生成的代码中有一些差异。
1.我创建了helper函数,而真实的生成的代码内联了它们。
1.在gcc生成的vtable中有RTTI(运行时类型信息)。这是动态强制转换之类的东西所需要的。
此外,如果使用
virtual
继承,事情会变得更有趣,因为现在vtable中有指向vtable的指针,而不仅仅是方法。如果我们扩展
Iswitch
接口,我们只会得到一个更大的array/vtable结构体,其中扩展的vtable的第一部分与基类匹配。对应于:
并且实现它的类必须指向一个完整的
Iswitch_extended
表和vtable
。所有这些技术在C++被指定之前就存在于C代码库中。这个想法是,编写这个样板文件很烦人,让编译器为您编写它是很好的。
缺点是有不止一种方法来完成所有这些。虽然每个主要的编译器都使用上述技术,但MFC使用基于开关的分派机制来实现其多态性。
上面的vtable方法的问题是每个具体类都需要一个O在MFC的情况下,需要重写的方法的数量可能很大(几乎所有的windows消息!)。所以消息ID不是一个巨大的函数表,而是传递给调度函数,它是菊花链的。如果一个子类想拦截这个对象上的那个消息,它停止链并返回函数指针(或直接在函数指针上执行消息)。
但是,由于语言中内置了一个固定的动态分派代码生成器,替代方法的成本要高得多,并且被忽视,即使它们对于特定的用例更好。
就我个人而言,我很期待反射,有了反射,我们就能像编译器一样高效地生成动态调度代码,但目标是不同的OO模型选择。