Delay true base class construction with placement new

500 views Asked by At

I'm asking if (and why) the following approach is a) legal and b) moral. I'm asking with emphasis on C++03, but notes on C++11 are welcome, too. The idea is to prevent derived classes that could themselves be default constructible from implementing stupid B::B(int foo) : A(foo) {} constructors.

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;
}

My gut is telling me, that something is fishy here. If any Derived class accesses Base members in its constructor, I want to be somewhere else, but otherwise I have trouble seeing valid reasons why the approach is bad.

Anyway, if you think this is an acceptable approach, how would to deal with reference members (something like int& Base::j)?

Note: This is a follow-up question to How can I fake constructor inheritance in C++03?.


Edit: I must have been distracted when posting the question. Of course, instead of delete b I meant b->~Base(). I blame low blood sugar!

2

There are 2 answers

4
David Rodríguez - dribeas On BEST ANSWER

The code is incorrect and triggers undefined behavior. Your Base class does not have a virtual destructor, which means that the delete b will cause Undefined Behavior.

The reasons for UB in the call to delete range from the fact that it will not release the derived resources (which seems to be the purpose of the code, ouch!), to the fact that it will try to release the allocated memory, which might work or not, depending on the layout of both objects. If it fails to deallocate the memory it will probably crash, if it succeeds the placement new call in the following line will try to initialize an object in memory that has already been released...

Even if you changed the code (trying to avoid deallocation problems) to:

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;
}

Where there is no delete and thus that particular source of Undefined Behavior is gone, the code is still undefined behavior (probably in too many ways to even mention). For once the call to the destructor is still UB, even if that was not, the fact that you recreated the Base type means that dynamic dispatch for that object will probably consider the object to be a Base rather than a Derived object (in the case of vtables, the vptr that points to the RunTime Type information will refer to Base, rather than Derived)

And there are probably two or three other things that can go wrong and I cannot think of right now...

1
Dennis Zickefoose On

delete b does not just call Bases destructor, it also deallocates the memory returned by new and calls Deriveds destructor [assuming you intended Base to have a virtual destructor... if you intended it to be non-virtual, the behavior is simply undefined to begin with]. Which means your subsequent use of placement new is constructing a whole new Base object in memory that is no longer valid, and you are never replacing the portion of Derived that you destroyed earlier. In short, nothing you have done even approaches correct behavior.

Frankly, I don't see what you're trying to do... why must Derived be default constructed, instead of just forwarding an argument? Its not stupid, its the way things are done.