C++ Standard

Previous Next
8

Something that Java, C++/CLI, and C# already has, is called delegating constructor. This is the ability for one constructor to delegate to another within the same class.C++03 does not have this capability because you cannot name a constructor within the same class in one of its constructor's initializer list. It can use constructors of other class types, including base classes and member objects.

In fact, it causes a serious problem when people who learn these other language come to C++, and try using it because what looks reasonable, will compile and run, but does not do what they think it does.

And it is more common then you think, because in many cases constructors may differ by the parameter list with common bodies. If the constructor can use default arguments, you could common the bodies. If it can't then you are out of luck today:

class A {};//has a conversion operator to double
class C
{
int x;
double y;
void startup();
public:
 //C(): x(0), y(1.23) { startup();} // I can collapse this into the following constructor and common up the body
 C(int i=0) : x(i), y(1.23) { startup();}
 C (const A& a) : x(0), y(a) {startup();} // I can't with this one
};


So having successfully refactored once, you might decide to try it in another case, turning this code into:
class X {
  Y y_;
  Z z_;
  void init();
public:
  X() { init() };
  X( int i ) : y_( i ),z_(1.23) { init(); }
  X( Widget w): y_(3.21), z_(1.23) { init(); }
};


this because this is similar to Java delegating constructors:

class X {
  Y y_;
  Z z_;
  void init();
public:
  X(){ init(); };
  X( int i ) : y_( i ),z_(1.23) { X(); }//I know both constructors are run because I can see it in a debugger
  X( Widget w): y_(3.21), z_(1.23) { X(); } //same here
};


This should work. Or should it ?

Well of course it doesn't, and the Mystery#1 of the Week is can someone tell me why not?

So may be we want to try something even more ambitious:

class X {
  Y y_;
  Z z_;
  void init();
  void *operator new(unsigned int, const X*);
  ... //assume we also have corresponding delete
public:
  X(){ init(); };
  X( int i ) : y_( i ),z_(1.23) { new (this) X; }//I know both constructors are run because I can see it in a debugger
  X( Widget w): y_(3.21), z_(w) { new (this) X; } // Same here
};


This looks even more promising and in fact it does everything I want it to do. Seems clever as I am using a placement new on the same object. In reality, it is an even worst solution then before. Mystery#2 of the Week is why is this worst?

In fact C++03 had no better solution which is either more cumbersome, or error prone.

C++0x will fix this for the sake of improved teachability and library building. The solution is to simply allow the call to the target constructor in the initializer list of the delegating constructor. We call the caller the delegating constructor and the callee is the target constructor

class X {
  Y y_;
  Z z_;
  void init();
public:
  X( int i, const Widget &w): y_(i),z_(w) { init();}
  X(){ init(); };
  X( int i ) : X( i, 1.23) {  }
  X( Widget w): X(3.21, w) {  }
};


The final accepted paper is in:
n1986



Dec 3, 2008 3:09 PM Click to view hstong's profile hstong

Mystery #1:
In

X( int i ) : y_( i ),z_(1.23) { X(); }
you are constructing a temporary and not delegating to the default constructor (as intended).

Dec 3, 2008 3:22 PM Click to view Michael_Wong's profile Michael_Wong in response to: hstong

Good work!. You are right. The temporary will get created, and destroyed at the end of scope. No delegation.

Dec 3, 2008 3:49 PM Click to view hstong's profile hstong

Mystery #2:
Among other things, destructors for the members of X will be called twice if init() throws an exception.
This is the worst solution because it has undefined behaviour.

Dec 3, 2008 4:38 PM Click to view Michael_Wong's profile Michael_Wong in response to: hstong

Even if an exception is not thrown, this code has problems ...
But you are right in that an exception would be even more problematic.

Dec 3, 2008 7:38 PM Click to view hstong's profile hstong in response to: Michael_Wong

The subobjects did not have their destructors called. There can be a memory leak if a subobject allocated memory dynamically.

Dec 4, 2008 9:14 AM Click to view Michael_Wong's profile Michael_Wong in response to: hstong

Not bad, and you are definitely dancing around the whole issue of object life times which is where the answer lies.

My response to this is that there is indeed a memory leak, but it need not be from any dynamic memory allocation of subobjects (which I assume you mean embedded members Y and Z).

Dec 4, 2008 7:19 PM Click to view hstong's profile hstong in response to: Michael_Wong

I think that the standard is a bit vague on what constitutes a "reuse" of storage. This makes it hard for me to tackle the issue with object lifetimes directly. Anyhow, another case where the placement new solution above will not work is when the object of type X is actually a base class subobject itself.

Dec 11, 2008 11:23 PM Click to view Michael_Wong's profile Michael_Wong in response to: hstong

I think I have to give it to you for the many cases you described. What I was looking for is not that far from what you were describing. It is that now in Mystery #2, we have an imbalance in the number of constructor and destructors, which is always a sign that something bad will happen. May be not immediately, but likely when you add any more complexity to the class (such as more members).

Just counting the construction of X, you will see that there are 2 constructor calls, and only one destructor call. This, as you said, is the worst solution. Well done.

C++ Standard

Bottom Banner