使用/不使用vtable初始化对象

时间:2015-01-23 13:14:32

标签: c++ constructor virtual variable-assignment vtable

假设我有一个分配缓冲区的池。

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();以便在创建的对象上调用构造函数。

这两个示例首先创建适当大小的缓冲区然后执行赋值,将其视为一个池,我根据我想要放入池中的对象数量预先分配内存。

也许我只是长时间地看着这个并迷惑自己:)

3 个答案:

答案 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

如果您不需要获取该对象的副本,则可以使用指针数组。