Reduce Repetitive Initialization Code in C++ Applications by Using Delegating Constructors

Introduction

Unlike a few other object-oriented programming languages, C++ programming doesn't allow a constructor to call another constructor of the same class. Consequently, programmers are often forced to repeat the same initialization code in every constructor. Such duplicate code introduces maintenance problems and compromises code readability. Fortunately, in 2006 the C++ programming standards committee voted into the Working Draft a proposal for adding delegating constructors to C++0x. In the following section I will show how to implement delegating constructors to reduce the amount of boilerplate code and simplify the design of your classes.

Duplicating Constructor Code

A C++ class may have more than one constructor. In many cases, those constructors perform identical initialization steps such as allocating memory and zeroing data members. In the following example, class M defines two constructors that are nearly identical:

  class M
  {
   int x, y;
   char *p buf;
  public:
   M(int v) : x(v), y(0) p(new char[MAX]) {} //#1
   M() : x(0), y(0), p(new char[MAX]) {} //#2
  };

Needless to say, such code reduplication is a maintenance problem.

Calling init()

Technically, constructor #1 performs exactly the same operations as does constructor #2 except the initialization of the member x. Ideally, you would want to move the identical bits of code into a single constructor. In C++03, the common workaround is to define a private member function called init(). Every constructor will call that function. Here's a revised version of the same class that uses init():

  class M
  {
   int x, y;
   char *p buf;
   void init() {x=y=0; p = new char [MAX];}
  public:
   M(int v)  {int(); x=v; }
   M() {init(); }
  };

Although init() eliminates code reduplication, it isn't an ideal solution. The name init() is just a convention; other programmers might choose different names for an initialization function or they might forget to call that function. In addition, the generated code is less efficient because the compiler applies certain optimizations to constructors' member initialization lists which it can't apply to an ordinary function.

Using Target Constructors

C++0x offers a neat solution to this problem. You can delegate the task of initialization to a single constructor known as the target constructor. Other constructors known as delegating constructors will invoke the target constructor first, and then perform additional operations if necessary.

Syntactically, a target constructor looks like an ordinary constructor. There's no explicit syntactic cue designating it as a target constructor. This is convenient because the same constructor can be used both as a target for another constructor, or as a delegating constructor, as you will shortly see. The delegating constructor invokes its target constructor like this:


  class M
  {
   int x, y;
   char *p buf;
  public:
   M(int v) : x(v), y(0),  p(new char [MAX];)  {} //#1 target ctor
   M() : M(0) {cout<<"I'm a delegating ctor"<<endl;} //#2 delegating ctor
  };

The member initialization list of constructor #2 invokes the target constructor #1, which initializes three data members of M. Inside the body of the delegating constructor you can perform additional steps -- writing to the console, for instance.

As said earlier, a delegating constructor may also serve as a target constructor. Consider:


  class M
  {
   int x, y;
   char *p buf;
  public:
   M(int v) : x(v), y(0), p(new char [MAX];)  {} //#1 target ctor
   M() : M(0) {cout<<"I'm a delegating ctor"<<endl;} //#2 delegating to#1
   M(double d) :M() 
    { cout<<d<<"and I'm delegating to #2!"<<endl;} //#3 delegating to #2
  };

Constructor #3 invokes constructor #2. However, constructor #2 is also a delegating constructor that invokes the target constructor #1. In other words, constructor #2 is both a target constructor and a delegating constructor:


  M m1(14.5); //invoke #3
  M m2(0); //invoke #1
  M m3; //invoke #2

To determine which constructors are invoked for every one of the three objects m1, m2, m3, all you have to do is find out which constructor is called first. In the case of m1, the argument 14.5 is of type double. Hence, constructor #3 is called first. In the case of m2, the argument is of type int, hence constructor #2 is called first.

If you're worried about performance, banish any concern. The compiler is able to optimize the generated code for delegating constructors quite effectively.

Conclusion

Delegating constructors offer a simple and intuitive solution to the problem of repeated initialization code. Presently, IBM's XLC++ 11.1 is among the few pioneers supporting delegating constructors. GCC also supports this feature but you will need to download a separate patch.



Comments

  • 'new' operator placement syntax

    Posted by dhoke on 08/11/2010 11:35am

    can currently be used (at least from my compilers) to call a constructor from another constructor.
    
    In article's example, changes might be:
      
      class M
      {
       int x, y;
       char *p buf;
      public:
       M(int v) : x(v), y(0) p(new char[MAX]) {} //#1
       M() : { new (this) M(0); } //#2
      };
      

    Reply
  • Default values

    Posted by sysfrank on 08/06/2010 09:41am

    The example looks like a job for default values to me. M(int v=0) { . . . }

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: August 20, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT When you look at natural user interfaces as a developer, it isn't just fun and games. There are some very serious, real-world usage models of how things can help make the world a better place – things like Intel® RealSense™ technology. Check out this upcoming eSeminar and join the panel of experts, both from inside and outside of Intel, as they discuss how natural user interfaces will likely be getting adopted in a wide variety …

  • Hybrid cloud platforms need to think in terms of sweet spots when it comes to application platform interface (API) integration. Cloud Velocity has taken a unique approach to tight integration with the API sweet spot; enough to support the agility of physical and virtual apps, including multi-tier environments and databases, while reducing capital and operating costs. Read this case study to learn how a global-level Fortune 1000 company was able to deploy an entire 6+ TB Oracle eCommerce stack in Amazon Web …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds