Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why did C++11 introduce delegating constructors?

Tags:

I cannot understand what the use is of delegating constructors. Simply, what cannot be achieve without having delegating constructors?

It can do something simple like this

class M  {  int x, y;  char *p; public:  M(int v) : x(v), y(0), p(new char [MAX]) {}  M(): M(0) {cout<<"delegating ctor"<<endl;} }; 

But I don't see it is worth introduce a new feature for such a simple thing? May be I couldn't recognize the important point. Any idea?

like image 294
Nayana Adassuriya Avatar asked Oct 05 '14 03:10

Nayana Adassuriya


People also ask

What is a delegating constructor?

Delegating constructors. Constructors are allowed to call other constructors from the same class. This process is called delegating constructors (or constructor chaining). To have one constructor call another, simply call the constructor in the member initializer list.

What are delegating constructors in C++?

Delegating constructors can call the target constructor to do the initialization. A delegating constructor can also be used as the target constructor of one or more delegating constructors. You can use this feature to make programs more readable and maintainable.

Why do we need constructors in CPP?

C++ Constructors. Constructors are methods that are automatically executed every time you create an object. The purpose of a constructor is to construct an object and assign values to the object's members. A constructor takes the same name as the class to which it belongs, and does not return any values.

What is the purpose of classes constructor?

A class constructor is a special member function of a class that is executed whenever we create new objects of that class. A constructor will have exact same name as the class and it does not have any return type at all, not even void.


2 Answers

Delegating constructors prevent code duplication (and all the possible errors and flaws that come with it : increased maintenance, decreased readability...), which is a good thing.

It is also the only way to delegate the initialization list (for members and bases initializations), i.e. you really can't replace this feature by having a shared Init() method for your constructors.


Examples:

1) Common initialization from N1986 proposal :

class X {   X( int, W& );   Y y_;   Z z_;  public:   X();   X( int );   X( W& );  };  X::X( int i, W& e ) : y_(i), z_(e) { /*Common Init*/ }  X::X() : X( 42, 3.14 )             { SomePostInitialization(); }  X::X( int i ) : X( i, 3.14 )       { OtherPostInitialization(); }  X::X( W& w ) : X( 53, w )          { /* no post-init */ }  

2) Delegation with both constructor and copy constructor, also from N1986 proposal :

class FullName {   string firstName_;   string middleName_;   string lastName_;   public:   FullName(string firstName, string middleName, string lastName);   FullName(string firstName, string lastName);   FullName(const FullName& name);  };  FullName::FullName(string firstName, string middleName, string lastName)   : firstName_(firstName), middleName_(middleName), lastName_(lastName)  {   // ...  }  // delegating copy constructor  FullName::FullName(const FullName& name)   : FullName(name.firstName_, name.middleName_, name.lastName_)  {   // ...  }  // delegating constructor  FullName::FullName(string firstName, string lastName)   : FullName(firstName, "", lastName)  {   // ...  }  

3) MSDN gives this example, with constructors performing argument validation (as commented, this design is debatable) :

class class_c { public:     int max;     int min;     int middle;      class_c() {}     class_c(int my_max) {          max = my_max > 0 ? my_max : 10;      }     class_c(int my_max, int my_min) {          max = my_max > 0 ? my_max : 10;         min = my_min > 0 && my_min < max ? my_min : 1;     }     class_c(int my_max, int my_min, int my_middle) {         max = my_max > 0 ? my_max : 10;         min = my_min > 0 && my_min < max ? my_min : 1;         middle = my_middle < max && my_middle > min ? my_middle : 5;     } }; 

Thanks to constructors delegation, it reduces to :

class class_c { public:     int max;     int min;     int middle;      class_c(int my_max) {          max = my_max > 0 ? my_max : 10;      }     class_c(int my_max, int my_min) : class_c(my_max) {          min = my_min > 0 && my_min < max ? my_min : 1;     }     class_c(int my_max, int my_min, int my_middle) : class_c (my_max, my_min){         middle = my_middle < max && my_middle > min ? my_middle : 5; } }; 

Links:

  • Delegating Constructors (r3) Proposal - N1986
  • Stroustrup C++ FAQ : Delegating constructors
like image 150
quantdev Avatar answered Nov 15 '22 13:11

quantdev


In addition to quantdev's excellent answer (which I have upvoted), I wanted to also demonstrate the exception safety issues of delegating constructors for those types which must explicitly acquire multiple resources in a constructor, and explicitly dispose of multiple resources in its destructor.

As an example, I will use simple raw pointers. Note that this example is not very motivating because the use of smart pointers over raw pointers will solve the problem more neatly than delegating constructors. But the example is simple. There still exists more complex examples that are not solved by smart pointers.

Consider two classes X and Y, which are normal classes, except that I've decorated their special members with print statements so we can see them, and Y has a copy constructor that might throw (in our simple example it always throws just for demonstration purposes):

#include <iostream>  class X { public:     X()     {         std::cout << "X()\n";     }      ~X()     {         std::cout << "~X()\n";     }      X(const X&)     {         std::cout << "X(const&)\n";     }      X& operator=(const X&) = delete; };  class Y { public:     Y()     {         std::cout << "Y()\n";     }      ~Y()     {         std::cout << "~Y()\n";     }      Y(const Y&)     {         throw 1;     }      Y& operator=(const Y&) = delete; }; 

Now the demo class is Z which holds a manually managed pointer to an X and a Y, just to create "multiple manually managed resources."

class Z {     X* x_ptr;     Y* y_ptr; public:     Z()         : x_ptr(nullptr)         , y_ptr(nullptr)     {}      ~Z()     {         delete x_ptr;         delete y_ptr;     }      Z(const X& x, const Y& y)         : x_ptr(new X(x))         , y_ptr(new Y(y))         {} }; 

The Z(const X& x, const Y& y) constructor as it stands is not exception safe. To demonstrate:

int main() {     try     {         Z z{X{}, Y{}};     }     catch (...)     {     } } 

which outputs:

X() Y() X(const&) ~Y() ~X() 

X got constructed twice, but destructed only once. There is a memory leak. There are several ways to make this constructor safe, one way is:

Z(const X& x, const Y& y)     : x_ptr(new X(x))     , y_ptr(nullptr) {     try     {         y_ptr = new Y(y);     }     catch (...)     {         delete x_ptr;         throw;     } } 

The example program now correctly outputs:

X() Y() X(const&) ~X() ~Y() ~X() 

However one can easily see that as you add managed resources to Z, this quickly gets cumbersome. This problem is solved very elegantly by delegating constructors:

Z(const X& x, const Y& y)     : Z() {     x_ptr = new X(x);     y_ptr = new Y(y); } 

This constructor first delegates to the default constructor which does nothing but put the class into a valid, resource-less state. Once the default constructor completes, Z is now considered fully constructed. So if anything in the body of this constructor throws, ~Z() now runs (unlike the previous example implementations of Z(const X& x, const Y& y). And ~Z() correctly cleans up resources that have already been constructed (and ignores those that haven't).

If you have to write a class that manages multiple resources in its destructor, and for whatever reasons you can't use other objects to manage those resources (e.g. unique_ptr), I highly recommend this idiom to manage exception safety.

Update

Perhaps a more motivating example is a custom container class (the std::lib doesn't supply all containers).

Your container class might look like:

template <class T> class my_container {     // ... public:     ~my_container() {clear();}     my_container();  // create empty (resource-less) state     template <class Iterator> my_container(Iterator first, Iterator last);     // ... }; 

One way to implement the member-template constructor is:

template <class T> template <class Iterator> my_container<T>::my_container(Iterator first, Iterator last) {     // create empty (resource-less) state     // ...     try     {         for (; first != last; ++first)             insert(*first);     }     catch (...)     {         clear();         throw;     } } 

But here is how I would do it:

template <class T> template <class Iterator> my_container<T>::my_container(Iterator first, Iterator last)     : my_container() // create empty (resource-less) state {     for (; first != last; ++first)         insert(*first); } 

If someone in code review called the latter bad practice, I would go to the mat on that one.

like image 39
Howard Hinnant Avatar answered Nov 15 '22 13:11

Howard Hinnant