Xeren Narcy Xeren Narcy - 3 months ago 17
C++ Question

control of base constructor in derived classes, potential double-initialization

Regarding this question and the answer to it there does seem to be an exception, but it raised more questions for me than answering them. Consider this:

#include <iostream>
using namespace std;
struct base {
virtual void test() {cout << "base::test" << endl;}
base() {test();}
virtual ~base() {}
};
struct derived : base {
virtual void test() {cout << "derived::test" << endl;}
derived() : base() {}
~derived() {}
};
int main() {
derived d;
return 0;
}


I naively thought this would only print either of the two messages. It actually prints both - first the base version then derived. This behaves the same on
-O0
and
-O3
settings, so it's not an optimization or lack thereof as far as I can tell.

Am I to understand that calling
base
(or higher / earlier classes' constructors) within a
derived
constructor, will not prevent the default
base
constructor (or otherwise) from being called beforehand?

That is to say, the sequence in the above snippet when constructing a
derived
object is:
base()
then
derived()
and within that
base()
again?

I know it doesn't make sense to modify the vtable just for the purposes of calling
base::base()
, back to what it was before
derived::derived()
was called, just for the sake of calling a different constructor. I can only guess that vtable-related things are hard-coded into the constructor-chain and calling previous constructors is literally interpreted as a proper method call (up to the most-derived object having been constructed in the chain so far)?

These minor questions aside, it raises two important ones:

1. Is calling a base constructor within a derived constructor always going to incur calling the default base constructor prior to the derived constructor being called in the first place? Is this not inefficient?

2. Is there a use-case where the default base constructor, per #1, shouldn't be used in lieu of the base constructor explicitly called in a derived classes' constructor? How can this be achieved in C++?

I know #2 sounds silly, after all you'd have no guarantee the state of the base class part of a derived class was 'ready' / 'constructed' if you could defer calling the base constructor until an arbitrary function call in the derived constructor. So for instance this:

derived::derived() { base::base(); }


... I would expect to behave the same way and call the base constructor twice. However is there a reason that the compiler seems to treat it as the same case as this?

derived::derived() : base() { }


I'm not sure. But these seem to be equivalent statements as far as observed effects go. It runs counter to the idea I had in mind that the base constructor could be forwarded (in a sense at least) or perhaps a better choice of word would be selected within a derived class using
:base()
syntax. Indeed, that notation requires base classes to be put before members distinct to the derived class...

In other words this answer and it's example (forget for a moment its C#) would call the base constructor twice? Although I understand why it would be doing that, I don't understand why it doesn't behave more "intuitively" and select the base constructor (at least for simple cases) and call it only once.

Isn't that a risk of double-initializing the object? Or is that part-and-parcel of assuming the object is uninitialized when writing constructor code? worst case do I now have to assume that every class member could potentially be initialized twice and guard against that?

I'll end with a horrible example - but would this not leak memory? should it be expected to leak?

#include <iostream>
using namespace std;
struct base2 {
int * member;
base2() : member(new int) {}
base2(int*m) : member(m) {}
~base2() {if (member) delete member;}
};
struct derived2 : base2 {
derived2() : base2(new int) {
// is `member` leaking?
// should it be with this syntax?
}
};
int main() {
derived2 d;
return 0;
}

Answer

but would this not leak memory? should it be expected to leak?

no. The sequence of operations will be:

derived2::derived2()
  auto p = new int
  base2::base2(p)
   base2::member = p

And for the destructor:

derived2::~derived2() (implied)
 base2::~base2()
  if (base2::member) { delete base2::member; }

One new, one delete. Perfect.

Don't forget to write correct assignment/copy constructors.