假设我有一个分配缓冲区的池。
int size = 10;
T* buffer = (T*) new char[size * sizeof(T)];
如果我现在想要将一些数据分配给缓冲区,我会执行以下操作。
buffer[0] = data;
现在我的问题是,具有vtable的对象和不具有vtable的对象的初始化有何不同。
从我所看到的,我可以毫无问题地将类分配给此缓冲区,并且只要我不调用任何虚函数,函数调用就可以正常工作。 e.g。
class A{
void function(){}
};
A a;
buffer[0] = a;
a.function(); // works
可是:
class B{
void function(){}
virtual void virtual_function(){}
};
B b;
buffer[0] = b;
b.function(); // does work
b.virtual_function() // does not work.
为什么非虚函数有效?
是因为函数是静态声明的,因为它是一个普通的类函数,因此在我们进行赋值时被复制了吗?
但是我需要在我创建的缓冲区上调用构造函数,以防止我需要确保虚函数也能正常工作。 new (buffer[0]) T();
以便在创建的对象上调用构造函数。
这两个示例首先创建适当大小的缓冲区然后执行赋值,将其视为一个池,我根据我想要放入池中的对象数量预先分配内存。
也许我只是长时间地看着这个并迷惑自己:)
答案 0 :(得分:2)
您的非虚拟功能"工作" (相对术语)因为它们不需要vtable查找。底层是依赖于实现的,但考虑执行非虚拟成员所需的内容。
你需要一个函数指针和一个this
。后者是显而易见的,但fn-ptr来自哪里?它只是一个普通的函数调用(期望this
,然后是任何提供的参数)。这里没有多态潜力。不需要vtable查找意味着编译器可以(并且经常)只是简单地获取我们认为是对象的地址,推送它,推送任何提供的args,并以普通的方式调用成员函数 - call
。编译器知道要调用哪个函数,并且不需要vtable-intermed。
在非法指针上调用非静态非虚拟成员函数时,这会导致头痛,这种情况并不少见。如果该功能是虚拟的,您通常会(如果您很幸运的话)在电话上爆炸。如果该功能是非虚拟的,那么你通常(如果你很幸运的话)在功能体内的某个地方爆炸,因为它试图访问不存在的成员数据(包括vtable) - 如果您的非虚拟呼叫是虚拟的,则执行指令)。
为了证明这一点,请考虑这个(显然是UB)的例子。试试吧。
#include <iostream>
class NullClass
{
public:
void call_me()
{
std::cout << static_cast<void*>(this) << '\n';
std::cout << "How did I get *here* ???" << '\n';
}
};
int main()
{
NullClass *noObject = NULL;
noObject->call_me();
}
输出(OSX 10.10.1 x64,clang 3.5)
0x0
How did I get *here* ???
当您分配原始内存并通过强制转换分配指针时,底线是没有vtable绑定到对象。如果要执行此操作,则需要通过placement-new构造对象。在这样做时,不要忘记你还必须通过调用它的析构函数来销毁对象(它与它占用的内存无关,因为你要单独管理它)。手动
最后,您调用的作业不会复制vtable。坦率地说,没有理由。正确构造的对象的vtable已经正确构建,并由给定对象实例的vtable指针引用。所述指针不参与对象复制,它有自己的语言标准要求。
答案 1 :(得分:1)
new char[...]
这不构造对象T(不调用构造函数)。 虚拟表在构造期间创建。
答案 2 :(得分:0)
问题不在于虚拟功能,更常见的是继承。因为缓冲区是A的数组,所以当你写:
B b;
buffer[0] = b;
首先构造一个B
对象(第一行),然后使用用A
(第二行)初始化的复制构造函数构造一个b
对象。
因此,当您稍后调用buffer[0].virtual_function()
时,您实际将虚拟函数应用于A
对象,而不是B
对象。
顺便说一下,直接调用b.virtual_function()
仍应正确调用B
版本,因为它已应用于真实B
对象:
B b;
buffer[0] = b;
b.virtual_function(); // calls B version
如果您不需要获取该对象的副本,则可以使用指针数组。