devxlogo

Use Delegating Constructors to Eliminate Initialization Code Reduplication

Use Delegating Constructors to Eliminate Initialization Code Reduplication

Many object-oriented programming languages allow a constructor to delegate its object initialization to another constructor of the same class. But in C++98, this isn’t permitted. Consequently, a C++ class with multiple constructors often contains duplicate initialization code. This causes maintenance problems and reduces code readability. Recently, the standardization committee approved a proposal to add delegating constructors to C++. While it may take some time before your favorite C++ compiler supports this feature, it’s still a good idea to learn how using delegating constructors can simplify the design of multi-constructor classes, reduce code reduplication, and eliminate bugs.


How do you delegate common initialization operations to a single constructor of a class, allowing other constructors to invoke that constructor?


Define a target constructor that takes care of common initialization operations and let other constructors invoke it.

Demonstrating the Problem
A C++ class may have multiple constructors. Often these constructors perform identical initialization steps before executing individual operations. In the worst case scenario, the identical initialization steps are copied and pasted in every constructor. Such code reduplication is a recipe for maintenance problems and bugs. Consider the following Message class:

class A;class Message{ char *buff;  int msg_id; size_t msg_length;public: explicit Message(int); explicit Message(const char *); explicit Message(const A&);};

Assume that each of the constructors first initializes the members buff, msg_id, and msg_length before performing individual operations. The code that initializes the three data members is repeated three times:

Message(int n) : buff(0), msg_id(0), msg_length(0)  {/*..*/}Message(const char* p) : buff(0), msg_id(0),msg_length(0)  {/*..*/}Message(const A& a) : buff(0), msg_id(0), msg_length(0)  {/*..*/}

If, due to design change, you need to add more members, change the type of existing members, or even reorder members, you’ll have to modify each of these constructors. Most C++ programmers avoid this code reduplication by moving the common initialization steps to a private member function that each constructor must call:

class Message{//.. void init()  {   buff=0;   msg_id=0;   msg_length=0; }public: explicit Message(int n) { init(); } explicit Message(const char * ptr) { init(); } explicit Message(const A& a) { init(); }};

This workaround eliminates code reduplication but it creates new problems:

  • Other member functions might accidentally call init(), thus causing unexpected results.
  • An init() call inside a constructor might occur in the wrong place, or it could accidentally occur twice.
  • References and const members require a member initialization list anyway so their initialization code is still repeated in each constructor.

Let’s see how delegating constructors solves this problem.

Delegating and Target Constructors
A delegating constructor uses a member initializer list that invokes another constructor (known as the target constructor) of the same class. Once the target constructor returns, the delegating constructor may perform additional initializations:

class Message{ char *buff;  int msg_id; size_t msg_length;public: //target constructor. Invoked by all other ctorsMessage() :  buff(0),msg_id(0),msg_length(0){}//#1 //the following three ctors invoke the target ctorexplicit Message(int) :   Message() {} //delegate the initialization to #1explicit Message(const char *p) :   Message() { /* additional code*/ } explicit Message(const A& a) :   Message() { /*additional code*/ } };

The new target constructor #1 performs the common initializations. The other three constructors delegate their initialization operations to the target constructor. Once the target constructor has finished, the delegating constructor may execute additional code.

Delegation Chaining
Notice that the target constructor is an ordinary constructor. Therefore, it’s possible to create a delegation chain as follows:

struct S{ S() {cout<<"first target"<

S has three constructors. When s1 is constructed, constructor #1 executes first, then #2 and finally #3. This output clearly shows the delegation order:

first targetsecond targetfinally, delegating ctor

Just as with ordinary functions, you need to ensure that the delegation chain doesn't lead to infinite recursion. This might happen if for instance constructor #3 invokes #2 which in turn invokes #3.

Overloading
Syntactically, an initialization list that contains a delegated constructor looks exactly like a C++98 member initialization list. So how can the compiler (and you) tell them apart? Consider:

class A{public: explicit A(int);};class B: public A{public: explicit B(int n) : A(n) {} //C++98 mem-init B() : B(0) {}//delegating ctor};

There's no confusion between the two because a target constructor always has the same name as its delegating constructors.

When you have multiple constructors with the same name, the standard overload resolution rules determine which target constructor will be used:

struct X{ explicit X(int)            {} #1 explicit X(bool)           {} #2 X() : X(true)              {} #3 calls #2 explicit X(int *p) : X(*p) {} #4 calls #1};

The delegating constructor #3 invokes the target constructor #2 because the argument true is of type bool. Similarly, the delegating constructor #4 invokes the target constructor #1 because *p is int.

Best of Both Worlds
Delegating constructors eliminate code reduplication in constructors while still ensuring that the common initialization steps are performed during the object's initialization. This has three advantages:

  • Reference and const members can be initialized in once place.
  • Other member functions can't invoke the target constructor accidentally.
  • The target constructor always runs once, before the delegating constructor's body.

In April 2006, the delegating constructors proposal was integrated into the current Working Paper of the C++ standard. This means that the next C++ standard (due in 2009) will support this feature. You won't have to wait that long for this feature, though. Vendors will probably start supporting it sooner, once the Final Candidate Document (due at the end of 2007) is published.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist