Channels ▼
RSS

Delegating Constructors?


Delegating Constructors?

I happened to look up just in time to see Kerry walk past my cubicle, looking somewhere between downcast and sad.

"Hey, what's up?" I asked him. "You okay?"

Kerry hesitated and then returned. "Hi. Did you ever have one of those days where it seemed like nothing you did was right?"

"Sure, plenty," I commiserated. "What's wrong?"

Kerry sighed, and then it all came out in a gush. From the flow of words, I distilled that he had recently had a conversation with Wendy about code factoring and reuse and had diligently tried to put it into practice, only to have his resulting code summarily skewered by Bob. "Here's a condensed version of what I wrote," said Kerry and showed me a file:

class X {
  Y y_;
public:
  X() { /* ... */ };
  // error, doesn't compile
  X( int i ) : y_( i ), X() { }
};
"See, both constructors do all the same work, except that one initializes y_ from an int and one default-constructs it. I didn't want to write all that stuff in the constructor body twice," Kerry explained, "so I thought I'd put the work code inside the default constructor and let the other constructor do the special stuff and delegate the rest of the work to the first constructor. But my compiler didn't accept the X() in that position, so I moved it into the constructor body. That compiled fine." He showed me:

class X {
  Y y_;
public:
  X() { /* ... */ };
  // compiles successfully,but...
  X( int i ) : y_( i ) { X(); }
};
"But," Kerry finished, "the common initialization doesn't stick. I know that both bodies are getting run because when I step into the second constructor in the debugger, it does go into the body of the first constructor, which is what I wanted. But after it does its work, the changes the first constructor made don't stick."

"Oh, I see," I nodded. "Did Bob tell you why this code wasn't doing what you thought?"

"Yes. He explained it all to me so clearly that I could understand it right away."

I was slightly dumbfounded. "He... he explained it clearly? And correctly? Bob? Our Bob?" I felt a glimmer of hope in my heart -- maybe there was hope for Bob yet! I was pleasantly surprised to discover that I was genuinely happy for Bob's progress, that I had not yet written Bob off in my heart despite all the other things that had happened since I'd joined the team. "Well, that's great, just fine! What did Bob say?"

"He said that I was calling the default constructor all right, but not on the this object. That the expression X() is just creating another unnamed temporary X object that just gets destroyed right away. It's a different X object, so that's why I saw my default constructor run -- but it was running on that other temporary X object, so that's why my own X object didn't get changed."

"Great! That's great! Bob is right, that's exactly right," I enthused. "Wow. This is wonderful. You know, he--"

"Yes, yes, I know," Kerry sighed. "Then Bob showed me the right way to do it, and he changed it to this:"

class X {
  Y y_;
public:
  X() { /* ... */ };
  // Bob's solution...?
  X( int i ) : y_( i ) { new (this) X; }
};
My heart skipped a beat. "Bob did that?"

"Yes. He showed me that this forces the X constructor to run on my own object's memory, and I understood immediately because I'd just been reading up on placement new. He was right, that's how to force the constructor to run on that specific memory, not on some other object." Kerry paused, read my darkening expression, and then frowned. "Uh... isn't that okay?"

My heart had stopped skipping and had started sinking instead. I must have groaned involuntarily, because suddenly Kerry looked even more concerned. "It's nothing," I waved it off, "uh, just some indigestion. Cafeteria food. But... but... but do you see that what Bob suggested as a workaround is actually far worse than your original code?"

Kerry blinked. "It is?"

Snap! After my emotional roller-coaster ride, I was too tired to jump even when the Guru's tome snapped shut loudly behind us. So, it appeared, was Kerry, who simply turned and looked sadly at the wiry, bespectacled Guru. I only looked sadly at Kerry. The Guru in turn looked sadly at both of us. It was a very sad moment, all things considered.

Moments passed thus mournfully. Finally the Guru broke the silence: "My children, it is an abomination," she agreed quietly, shaking her head. "Truly an abhorrent thing."

"Ugly," I softly agreed.

Kerry spoke up gamely: "Well, he showed me... he actually compiled the code and ran it, and it was all okay. Not only were there no compiler errors or warnings, but it actually did modify my own X object. It did; I saw it; he stepped into it."

The Guru sighed. "And did you check the value of y_?"

"Y, oh Y," I added sadly to keep the mood going.

"I... well, I was going to play with it some more like that, but Bob shooed me out of his office."

"You will find," the Guru continued, "that your y_ is not as you wish it. But that is not the most basic problem. Consider the following code:"

X x( 42 );
"In this code," she asked, "how many X and Y constructors and destructors are executed? My novice?" Kerry was silent, so she turned to me: "Apprentice, would you answer?"

"It is an abomination," I agreed. "There will be two X constructors run -- one's execution nested within the other's, actually -- but only one X destructor run. There will be two Y constructors run -- serially, this time -- but still only one Y destructor run. It is an abhorrent thing."

"Correct. Your analysis?"

"This anti-idiom just has so many problems it's hard to know where to start," I replied. "Never mind the problem with y_ not getting set right, because the X default constructor will clobber it with another Y construction; this violates all of the C++ object model and object lifetime assumptions and guarantees. Among other things, constructors and destructors must match up. This anti-idiom deliberately breaks that symmetry, and though it might seem to work for some classes, it'll certainly blow up as soon as X or Y manages resources or tries to do anything more complicated than just store plain old data. It's horrible, just horrible."

"But our compiler allows it," Kerry said weakly. "Bob showed me."

"All compilers that conform to the Holy Standard will allow it," the Guru agreed. "The code is not illegal, merely immoral. Just because one can, does not mean one should. Look," she continued somewhat uncharacteristically, momentarily letting the Guru shtick slip but quickly recovering, "the motivation here is understandable. I mean... my children, even the Prophets have wished for the ability to perform such feats in a safe and legal way. But to do it according to our Standard, we must write our own forwarding code." She demonstrated on the whiteboard, writing in a spidery script:

class X {
  void CommonInit();
  Y member1_;
  Z member2_;

public:
  X();
  X( int );
  X( SomethingElse );
};

X::X()
  : member1_( 42 )
  , member2_( 3.14 )
{
  CommonInit();
}

X::X( int i )
  : member1_( i )
  , member2_( 3.14 )
{
  CommonInit();
}

X::X( SomethingElse e )
  : member1_( 53 )
  , member2_( e )
{
  CommonInit();
}
"This achieves most of what a true delegating constructor would achieve, except for two things. First, we must still accept some redundancy in the constructor bodies, which must all remember to call CommonInit(). In this particular example, one of the constructors could be replaced with a default parameter; the other cannot, not without changing the calling semantics. But default parameters are not powerful enough to achieve a fully general solution to this problem."

"Okay," Kerry agreed. "But you said there were two things?"

"Indeed. Second, each constructor must still initialize the members, which adds redundant code. This aspect is even more difficult to work around consistently in the general case. It would be attractive to be able to delegate also the initialization of the members, but this we cannot do in Standard C++ without new language support, because once we are in a non-constructor member function, it is altogether too late -- members have already been constructed and can only be assigned or otherwise modified, which may not amount to the same thing and is likely not as efficient."

"What kind of language support would make this work the way we want?" I asked. "Just thinking about the future, of course, not something that would compile today."

"Ah. What I mean is this: the Holy Standard may eventually support something like the very first syntax I believe the young one attempted, namely delegation to a sibling constructor called in the initializer list, which does not compile today. That is, it would be nice if a future version of the Holy Standard allowed us to write something like this:"

class X {
  X( int, const SomethingElse& );
  Y member1_;
  Z member2_;

public:
  X();
  X( int );
  X( SomethingElse );
};

X::X( int i, const SomethingElse& e )
 : member1_(i), member2_(e)
{
  /*Common Init*/
}

X::X() : X( 42, 3.14 ) { }

X::X( int i ) : X( i, 3.14 ) { }

X::X( SomethingElse e ) : X( 53, e ) { }
"But today, the Standard does not allow this. The best that can be done is delegation to a common initialization function called from within each constructor, and to repeat the member initializations. It does not allow the same expressiveness, especially in the control of the construction of members, but it is the best we can do today."

She reopened her tome and glided away, saying: "Show me your writings on the morrow. I may have to have another chat with Bob..." She seemed to shiver just as she turned the corner and disappeared.

About the Authors

Herb Sutter (<www.gotw.ca>) is convener of the ISO C++ standards committee, author of the acclaimed books Exceptional C++ and More Exceptional C++, and one of the instructors of The C++ Seminar (<www.gotw.ca/cpp_seminar>). In addition to his independent writing and consulting, he is also C++ community liaison for Microsoft.

Jim Hyslop is a senior software designer with over 10 years programming experience in C and C++. Jim works at Leitch Technology International Inc., where he deals with a variety of applications, ranging from embedded applications to Windows programs. He can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.