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?
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.
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.
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.
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.
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With