Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do C++11 delegated ctors perform worse than C++03 ctors calling init functions?

[this question has been highly edited; pardon that, I've moved the edits into an answer below]

From Wikipedia (subarticle included) on C++11:

This [new delegating constructors feature] comes with a caveat: C++03 considers an object to be constructed when its constructor finishes executing, but C++11 considers an object constructed once any constructor finishes execution. Since multiple constructors will be allowed to execute, this will mean that each delegating constructor will be executing on a fully constructed object of its own type. Derived class constructors will execute after all delegation in their base classes is complete."

Does this mean that delegation chains construct a unique temporary object for every link in a ctor delegation chain? That kind of overhead just to avoid a simple init function definition would not be worth the additional overhead.

Disclaimer: I asked this question, because I'm a student, but the answers thus far have all been incorrect and demonstrate a lack of research and/or understanding of the research referenced. I've been somewhat frustrated by this, and as a result my edits and comments have been hastily and poorly composed, mostly over smart phone. Please excuse this; I hope I've minimized that in my answer below, and I have learned that I need to be careful, complete, and clear in my comments.

like image 811
okovko Avatar asked Oct 14 '15 02:10

okovko


3 Answers

No. They are equivalent. The delegating constructor behaves like an ordinary member function acting on the Object constructed by the previous constructor.

I couldn't find any information explicitly supporting this in the proposal for adding delegating constructors, but creating copies is not possible in the general case. Some classes may not have copy constructors.

In Section 4.3 - Changes to §15, the proposed change to the standard states:

if the non‐delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object’s destructor will be invoked.

This implies that the delegating constructor works on a completely constructed object (depending on how you define that) and allows the implementation to have delegating ctors work like member functions.

like image 84
nishantjr Avatar answered Nov 10 '22 02:11

nishantjr


Chained delegating constructors in C++11 do incur more overhead than the C++03 init function style!

See C++11 standard draft N3242, section 15.2. An exception may occur in the execution block of any link in the delegation chain, and C++11 extends existing exception handling behavior to account for that.

[text] and emphasis mine.

An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects ..., that is, for subobjects for which the principal constructor (12.6.2) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object’s [treated like a subobject as above] destructor will be invoked.

This is describing delegating ctors' consistency with the C++ object stack model, which necessarily introduces overhead.

I had to get familiar with things like how the stack works on a hardware level, what the stack pointer is, what automatic objects are, and what stack unwinding is, to really understand how this works. Technically these terms/concepts are implementation defined details, so N3242 does not define any of these terms; but it does use them.

The gist of it: Objects declared on the stack are allocated onto memory, and the executable handles the addressing and cleanup for you. The implementation of the stack was simple in C, but in C++, we have exceptions, and they demand an extension of C's stack unwinding. Section 5 of a paper by Stroustrup* discusses the need for extended stack unwinding, and the necessary additional overhead introduced by such a feature:

If a local object has a destructor, that destructor must be called as part of the stack unwinding. [A C++ extension of stack unwinding for automatic objects requires] ...an implementation technique that (in addition to the standard overhead of establishing a handler) involves only minimal overhead.

It's this very implementation technique and overhead that you add into your code for every link in your delegation chain. Every scope has the potential for an exception, and every constructor has its own scope, so every constructor in the chain adds overhead (as compared to an init function that only introduces one additional scope).

It's true that the overhead is minimal, and I'm sure that sane implementations optimize simple cases to remove that overhead. However, consider a case where you've got a 5 class inheritance chain. Let's say each of these classes has 5 constructors, and within each class, these constructors call each other in a chain to reduce redundant coding. If you instantiate an instance of the most derived class, you will incur the above described overhead up to 25 times, whereas the C++03 version would've incurred that overhead up to 10 times. If you make these classes virtual and multiply inheriting, this overhead will increase related to the accumulation of those features, as well as those features themselves introducing additional overhead. The moral here, is that as your code scales, you will feel the bite of this new feature.

*The Stroustrup reference was written a long time ago, to motivate discussion on C++ exception handling and defines potential (not necessarily) C++ language features. I chose this reference over some implementation specific reference because it is human readable, and 'portable.' My core use of this paper is section 5: specifically the discussion of the need for C++ stack unwinding, and the necessity of its overhead incurrence. These concepts are legitimized within the paper, and are valid today for C++11.

like image 29
okovko Avatar answered Nov 10 '22 03:11

okovko


Class constructors have two parts, a member initializer list and a function body. With constructor delegation, the initializer list and function body of the delegated (target) constructor is first executed. After that, the function body of the delegating constructor is executed. You may, in certain cases, consider an object to be fully constructed when both the initializer list and the function body of some constructor are executed. That's why the wiki says each delegating constructor will be executing on a fully constructed object of its own type. In fact, the semantics can be more accurately described as:

...the function body of each delegating constructor will be executing on a fully constructed object of its own type.

However, the delegated constructor may only partially construct the object, and is designed to be invoked by other constructors only other than to be used alone. Such a constructor is usually declared private. So, it may not always be appropriate to consider the object to be fully constructed after the execution of the delegated constructor.

Anyway, since only a single initializer list is performed, there is no such overhead as you have mentioned. Following are quoted from cppreference:

If the name of the class itself appears as class-or-identifier in the member initializer list, then the list must consist of that one member initializer only; such constructor is known as the delegating constructor, and the constructor selected by the only member of the initializer list is the target constructor

In this case, the target constructor is selected by overload resolution and executed first, then the control returns to the delegating constructor and its body is executed.

Delegating constructors cannot be recursive.

like image 36
Lingxi Avatar answered Nov 10 '22 02:11

Lingxi