使用placement new延迟真正的基类构造

时间:2011-11-03 14:50:28

标签: c++ placement-new construction

我在问以下方法是否(以及为什么)a)合法和b)道德。我要强调的是C ++ 03,但也欢迎关于C ++ 11的注释。我们的想法是防止派生类本身可以默认构造为实现愚蠢的B::B(int foo) : A(foo) {}构造函数。

class Base {
  private:
    int i;
    Base(int i) : i(i) {}
  protected:
    Base() {}
  public:
    static Base* create(int i);
};
class Derived : public Base {
};

Base* Base::create(int i) {
  Derived* d = new Derived();
  Base* b = static_cast<Base*>(d);
  delete b;
  new(b) Base(i);
  return d;
}

我的直觉告诉我,这里有些东西可疑。如果任何Derived类在其构造函数中访问Base成员,我想要在其他地方,但否则我很难看到为什么这种方法不好的正确理由。

无论如何,如果您认为这是一种可接受的方法,那么如何处理参考成员(如int& Base::j)?

注意:这是How can I fake constructor inheritance in C++03?的后续问题。


修改:发帖时我一定是分心了。当然,我不是delete b而是b->~Base()。我责备低血糖!

2 个答案:

答案 0 :(得分:3)

代码不正确并触发未定义的行为。您的Base类没有虚拟析构函数,这意味着delete b将导致未定义的行为。

调用delete时UB的原因在于它不会释放派生资源(这似乎是代码的目的,哎哟!),根据两个对象的布局,它将尝试释放分配的内存,这可能有效或无效。如果它无法释放内存,它可能会崩溃,如果它成功放置,则以下行中的新调用将尝试初始化已经释放的内存中的对象...

即使您将代码(试图避免重新分配问题)更改为:

Base* Base::create(int i) {
   Derived *d = new Derived;
   Base * b = static_cast<Base*>(d);
   b->~Base();                       // destruct, do not deallocate
   new (b) Base(i);
   return d;
}

如果没有delete并因此未定义行为的特定源已消失,则代码仍然是未定义的行为(可能有太多方法甚至没有提及)。一旦对析构函数的调用仍然是UB,即使不是这样,重新创建Base类型的事实意味着该对象的动态调度可能会认为该对象是Base而不是Derived而不是vtable对象(在vptr s的情况下,指向RunTime类型信息的Base将引用Derived,而不是{{1}})

可能还有两三种其他问题可能出错,我现在想不到......

答案 1 :(得分:1)

delete b不只是调用Base的析构函数,它还会释放new返回的内存并调用Derived的析构函数[假设您打算Base有一个虚拟的析构函数...如果你打算将它作为非虚拟的,那么这个行为只是以未开始的方式定义]。这意味着您随后使用placement new会在内存中构建一个不再有效的全新Base对象,并且您永远不会替换之前销毁的Derived部分。简而言之,你所做的一切甚至都没有达到正确的行为。

坦率地说,我看不出你要做什么...为什么必须Derived默认构建,而不是仅仅转发参数?它不是愚蠢的,它是事情的方式。