lundi 29 août 2016

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;
}

Aucun commentaire:

Enregistrer un commentaire