8

各位晚上好。
一个代码片段将值一千字:

// 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]

事实上,它触发了未定义的行为。您可以在 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] 中提到的案例是否定义得足够好,以至于我可以编写适用于类子集的代码(例如,没有虚拟继承)?

4

1 回答 1

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. “存储”变量作为数组分配在堆栈上

  2. print() 必须是虚拟方法

  3. 编译器选项应该是 -O2

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

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

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

注意: 此答案中给出的程序的实际输出与 OP 的不同。它不显示分段错误。但是,运行成功时会打印“Succeed”,在Coliru 上运行时不会显示。

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

于 2014-07-27T21:34:57.397 回答