Calling a constructor of the base class from a subclass' constructor body

前端 未结 4 1537
感动是毒
感动是毒 2020-12-15 10:07

I was under impression that it\'s impossible, see for example: Calling the constructor of the base class after some other instructions in C++
But the following program

相关标签:
4条回答
  • 2020-12-15 10:40

    The call inside the child class constructor is not calling the base class constructor, it is creating a temporary, unnamed and new object of type Person. It will be destroyed as the constructor exits. To clarify, your example is the same as doing this:

    Child() { c = 1; Person tempPerson; }
    

    Except in this case, the temporary object has a name.

    You can see what I mean if you modify your example a little:

    class Person
    {
    public:
        Person(int id):id(id) { std::cout << "Constructor Person " << id << std::endl; }
        ~Person(){ std::cout << "Destroying Person " << id << std::endl; }
        int id;
    };
    
    class Child : public Person
    {
    public:
        Child():Person(1) { c = 1; Person(2); }
    int c;
    };
    
    int main() {
    Child child;
    
    Person(3);
    return 0;
    }
    

    This produces the output:

    Constructor Person 1
    Constructor Person 2
    Destroying Person 2
    Constructor Person 3
    Destroying Person 3
    Destroying Person 1
    
    0 讨论(0)
  • 2020-12-15 10:42

    The answers to this question while usually technically true and useful, don't give the big picture. And the big picture is somewhat different than it may seem :)

    1. The base class's constructor is always invoked, otherwise in the body of the derived class's constructor you'd have a partially constructed and thus unusable object. You have the option of providing arguments to the base class constructor. This doesn't "invoke" it: it gets invoked no matter what, you can just pass some extra arguments to it:

      // Correct but useless the BaseClass constructor is invoked anyway
      DerivedClass::DerivedClass() : BaseClass() { ... }
      // A way of giving arguments to the BaseClass constructor
      DerivedClass::DerivedClass() : BaseClass(42) { ... }
      
    2. The C++ syntax to explicitly invoke a constructor has a weird name and lives up to this name, because it's something that's very rarely done - usually only in library/foundation code. It's called placement new, and no, it has nothing to do with memory allocation - this is the weird syntax to invoke constructors explicitly in C++:

      // This code will compile but has undefined behavior
      // Do NOT do this
      // This is not a valid C++ program even though the compiler accepts it!
      DerivedClass::DerivedClass() { new (this) BaseClass(); /* WRONG */ }       
      DerivedClass::DerivedClass() { new (this) BaseClass(42); /* WRONG */ }
      // The above is how constructor calls are actually written in C++.
      

      So, in your question, this is what you meant to ask about, but didn't know :) I imagine that this weird syntax is helpful since if it were easy, then people coming from languages where such constructor calls are commonplace (e.g. Pascal/Delphi) could write lots of seemingly working code that would be totally broken in all sorts of ways. Undefined behavior is not a guarantee of a crash, that's the problem. Superficial/obvious UB often results in crashes (like null pointer access), but a whole lot of UB is a silent killer. So making it harder to write incorrect code by making some syntax obscure is a desirable trait in a language.

    3. The "second option" in the question has nothing to do with constructor "calls". The C++ syntax of creating a default-constructed instance of a value of BaseClass object is:

      // Constructs a temporary instance of the object, and promptly
      // destructs it. It's useless.
      BaseClass();
      // Here's how the compiler can interpret the above code. You can write either
      // one and it has identical effects. Notice how the scope of the value ends
      // and you have no access to it.
      {
        BaseClass __temporary{};
      }
      

      In C++ the notion of a construction of an object instance is all-permeating: you do it all the time, since the language semantics equate the existence of an object with that object having been constructed. So you can also write:

      // Constructs a temporary integer, and promptly destructs it.
      int();
      

      Objects of integer type are also constructed and destructed - but the constructor and destructor are trivial and thus there's no overhead.

      Note that construction and destruction of an object this way doesn't imply any heap allocations. If the compiler decides that an instance has to be actually materialized (e.g. due to observable side effects of construction or destruction), the instance is a temporary object, just like the temporaries created during expression evaluation - a-ha, we notice that type() is an expression!

      So, in your case, that Person(); statement was a no-op. In code compiled in release mode, no machine instructions are generated for it, because there's no way to observe the effects of this statement (in the case of the particular Person class), and thus if no one is there to hear the tree fall, then the tree doesn't need to exist in the first place. That's how C++ compilers optimize stuff: they do lot of work to prove (formally, in a mathematical sense) whether the effects of any piece of code may be unobservable, and if so the code is treated as dead code and removed.

    0 讨论(0)
  • 2020-12-15 10:52

    You can't call it from the body of the child constructor, but you can put it into the initializer list:

    public:
        Child() : Person() { c = 1; }
    

    Of course it's not helpful to call the default constructor of the parent because that will happen automatically. It's more useful if you need to pass a parameter to the constructor.

    The reason you can't call the constructor from the body is because C++ guarantees the parent will be finished constructing before the child constructor starts.

    0 讨论(0)
  • 2020-12-15 11:00

    The following is an excerpt from "Accelerated C++": "Derived objects are constructed by:
    1. Allocating space for the entire object (base class members as well as derived class members);
    2. Calling the base-class constructor to initialize the base-class part of the object;
    3. Initializing the members of the derived class as directed by the constructor initializer;
    4. Executing the body of the derived-class constructor, if any."

    Summarizing the answers and comments: Calling a constructor of the base class from a subclass' constructor body is impossible in the sense that #2 above must precede #4. But we still can create a base object in the derived constructor body thus calling a base constructor. It will be an object different from the object being constructed with the currently executed derived constructor.

    0 讨论(0)
提交回复
热议问题