放置新的和继承

时间:2014-07-24 18:01:37

标签: c++ inheritance c++11 placement-new upcasting

大家晚上好。
一段代码片段胜过千言万语:

// Storage suitable for any of the listed instances
alignas(MaxAlign<Base, Derived1, Derived2>::value)
char storage[MaxSize<Base, Derived1, Derived2>::value];

// Instanciate one of the derived classes using placement new
new (storage) Derived2(3.14);


// Later...

// Recover a pointer to the base class
Base &ref = *reinterpret_cast<Base*> (storage);

// Use its (virtual) functions
ref.print();

// Destroy it when we're done.
ref.~Base();

正如您所看到的,我只想通过其基类访问实例,而不实际存储基类指针。请注意,在第二部分中,Derived2类型信息将丢失,因此我只剩下storage并保证其中包含一个派生实例。

由于placement new永远不会调整目标指针,因此归结为使用reinterpret_cast向上转换为基类。现在我知道这很危险,因为在某些情况下,更合适的static_cast会调整指针。 [1]

事实上,它会触发未定义的行为。你会找到完整的代码here on Coliru(g ++ 4.9.0),它会在运行时迅速崩溃。同时在我的电脑上(g ++ 4.8.2),一切都很好。请注意,在g ++ 4.9上,在调用函数之前输出两个指针显示相同的值......并且有效。

所以我试图向后解决问题:轻推派生实例,使指向Base的指针等于storage

void *ptr = static_cast<Derived2*>(reinterpret_cast<Base*>(storage));
new (ptr) Derived2(3.14);

没有运气。这个东西在g ++ 4.8上仍然运行良好,并且在g ++ 4.9上岌岌可危。

编辑:想到它,上面并不那么聪明。因为,如果派生实例在 storage之前结束,那该怎么办...

所以我的问题是:我正在尝试实现的解决方案吗?或者,[1]中提到的案例是否足够明确,我可以编写可以使用类子集的代码(比如,没有虚拟继承)?

1 个答案:

答案 0 :(得分:3)

我刚刚修改了你的代码,似乎reinterpret_cast不是问题所在。 可以复制此错误的最小代码就是这样

Coliru链接:http://coliru.stacked-crooked.com/a/dd9a633511a3d08d

#include <iostream>

struct Base { 
    virtual void print() {}
};

int main(int, char**) {
   Base storage[1];
   storage[0].print();

   std::cout <<"Succeed";
   return 0;
}

触发此错误的充分条件是

  1. &#34;存储&#34;变量在堆栈上作为数组分配

  2. print()必须是虚方法

  3. 编译器选项应为-O2

  4. 如果使用-O1,程序将编译并运行没有问题。

    此外,这个错误似乎只在用g ++ 4.9.0编译时出现。 如果使用VS2012 / 2013或g ++ 4.7.2编译它可以正常运行(您可以在http://www.compileonline.com/compile_cpp_online.php上进行测试)

    从上面的判断来看,我认为这可能是编译器特定的问题。

    注意: 本答案中给出的程序的实际输出与OP不同。它不显示分段错误。但是,当成功运行时,它应该打印&#34;成功&#34;,当它在Coliru上运行时不会显示。

    修改 修改了代码以复制错误。不需要派生类。