2

Im reading this article on constructors for c++

We recommend that you be careful when you call virtual functions in constructors. Because the base class constructor is always invoked before the derived class constructor, the function that's called in the base constructor is the base class version, not the derived class version. In the following example, constructing a DerivedClass causes the BaseClass implementation of print_it() to execute before the DerivedClass constructor causes the DerivedClass implementation of print_it() to execute:

the example:

    class BaseClass {
    public:
        BaseClass() {
            print_it();
        }
        virtual void print_it() {
            cout << "BaseClass print_it" << endl;
        }
    };

    class DerivedClass : public BaseClass {
    public:
        DerivedClass() {
            print_it();
        }
        virtual void print_it() {
            cout << "Derived Class print_it" << endl;
        }
    };

    int main() {

        DerivedClass dc;
    }

Here's the output:

BaseClass print_it
Derived Class print_it

I tried this code and the output is as stated above. However I also tried the same example without the virtual keyword:

    class BaseClass {
    public:
        BaseClass() {
            print_it();
        }
        void print_it() {
            cout << "BaseClass print_it" << endl;
        }
    };

    class DerivedClass : public BaseClass {
    public:
        DerivedClass() {
            print_it();
        }
        void print_it() {
            cout << "Derived Class print_it" << endl;
        }
    };

    int main() {

        DerivedClass dc;
    }

and got the same result.

So what is the difference and what is the danger they are warning for?

@marked as duplicate:

This question is different as the consturctors both call the virtual method instead of one constructor calling the virtual method.

Sven van den Boogaart
  • 11,833
  • 21
  • 86
  • 169
  • 1
    The danger is: in constructors and destructors virtual functions are not (virtual that is). – Jesper Juhl Jul 29 '16 at 18:45
  • 2
    Not quite correct. They are virtual, but when a constructor runs, *this is an instance of the class of the running constructor, and not an instance of the class that it will eventually belong to. So which constructor is run may be surprising. – gnasher729 Jul 29 '16 at 18:48
  • @gnasher729 could you explain what im supposed to expect, because I was expecting this result and don't see the danger. Not knowing what others expect might end up in misunderstandings. – Sven van den Boogaart Jul 29 '16 at 18:58
  • 2
    I've never understood why, in books/tutorials/etc. giving this warning and explaining this "danger", they don't describe it as a _positive_ and _expected_ result of the _only proper model_ for constructing objects of a derived class: objects (i.e., instances) are built incrementally from the most base class first, then the next class in the deriviation chain, and on until the most derived class is built. Naturally, when building the base class (first), only the base class's methods are available. Duh! Java and (unfortunately) C# are doing it wrong. _Their_ behavior is what's unexpected. – davidbak Jul 29 '16 at 19:11
  • @davidbak: I didn't even know Java and C# did it differently. That's sad to hear. :( I can't imagine how that would work. – Lightness Races in Orbit Jul 29 '16 at 19:12
  • 2
    @LightnessRacesinOrbit: It's because, all the specification text notwithstanding, Java and C# do not have real constructors, only initializers. In those languages, the object is fully constructed (with all zero/null member values present for all members at all derivation levels) and has its final dynamic type before any user-specified initialization takes place. – Ben Voigt Jul 29 '16 at 19:16
  • @BenVoigt: It doesn't seem like that would make it any more logical or any less surprising. It just makes it "safe". – Lightness Races in Orbit Jul 29 '16 at 19:17
  • @LightnessRacesinOrbit - in Java and C# the designers would like you to believe they construct _ab initio_ the instance of the class you're creating. (E.g,. the vtable is initialized once, to its final result.) Thus, virt functions call the most derived implementation. But this is inconsistent. They don't explain why, in that case, they nevertheless run constructors from base class through the chain to most derived. Why do they execute (implicitly) base class constructors at all? – davidbak Jul 29 '16 at 19:19
  • @LightnessRacesinOrbit - furthermore, when chaining to a base class constructor from a derived class, they disallow passing in a field of the derived class as a parameter to the base class constructor - e.g., see [here](http://stackoverflow.com/a/18719450/751579) - because you can't reference the current instance at that point. But why not? As @ BenVoight explains, everything is given a default initialization (which doesn't happen in C++) so all access to the current instance is "safe", as you say. Arrgh. – davidbak Jul 29 '16 at 19:24
  • 1
    @davidbak: For that matter, C++ allows passing a *pointer or reference* to a subobject of a derived class into other subobject constructor(s), but actually dereferencing it is illegal until the reference's target gets fully constructed. – Ben Voigt Jul 29 '16 at 19:28
  • @SvenB: That doesn't make your question different. (Unless you were surprised by the behavior of the Derived constructor). – Ben Voigt Jul 29 '16 at 19:43
  • @Sven it sounds like you don't understand virtual calls – Ben Voigt Jul 29 '16 at 21:31

1 Answers1

7

There is no difference. That's the danger.

If you did not know better then you might expect this instead:

Derived Class print_it
Derived Class print_it

The expectation is there because if you call the virtual print_it() from functions in Base, polymorphism means you'll usually get the Derived version instead.

But, when you write it in the Base constructor, the Base part of the object is still under construction, and the "dynamic type" of the object under construction is still Base, not Derived. So you do not get the usual polymorphic behaviour.

The article is warning you about this fact.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • The important thing is "construction of the Derived part of the object hasn't yet begun" (when) not "a function call written in the Base constructor" (where). If you write `Base::Base() { Base::nonvirt(); } void Base::nonvirt() { this->virt(); }` this will also reach `Base::virt()` and a later call to `nonvirt()` after construction finished will reach `Derived::virt()`. – Ben Voigt Jul 29 '16 at 19:19