4

Consider the following sample code below:

#include <iostream>

using namespace std;

class base
{
   public:
      base()
      {
         cout << "ctor in base class\n";
      }
};

class derived1 : public base
{
   public:
      derived1()
      {
         cout <<"ctor in derived class\n";
      }
};

int main()
{
   derived1 d1obj;
   return 0;
}

Questions

  1. When d1obj is created, the constructors are invoked in the order of derivation : base class constructor is called first and then the derived class constructor. Is this done because of the following reason : In-order to construct the derived class object the base class object needs to be constructed first?

  2. Does d1obj contains a base class object ?

I am adding one more question

3) When d1obj is created, the control first reaches the base class constructor and then it goes to the derived class constructor? Or is it the other way round : It first reaches the derived class constructor, finds that it has base class and so the control goes to the constructor in base class?

nitin_cherian
  • 6,405
  • 21
  • 76
  • 127
  • 1
    It might be more useful to ask content questions rather than decision questions. – Kerrek SB Dec 07 '11 at 16:14
  • You should get into the habit of explicitly calling the parent's ctor in the derived classes. Let say the base did not provide a default ctor then this code would not compile. But it woudl if you called the non-default ctor in the initializer list from the derived class. – danca Dec 07 '11 at 16:21
  • 3
    I was trying to develop the question based on the input from SO members, meanwhile affirming my belief on the C++ beginner concepts. A beginner coming to SO will find this question useful. Moreover no where in SO does it says that `if one can find the question in C++ faq, he should not ask it here in SO` – nitin_cherian Dec 07 '11 at 16:33

5 Answers5

7

1) Yes, bases are constructed first, then non-static data members, then the constructor of the derived class is called. The reason is so that the code in the constructor of this class can see and use a fully-constructed base.

2) Yes. You can take this entirely literally: within the memory assigned to the derived class object, there is a region called the "base class sub-object". A derived class object "contains" a base class subobject in precisely the same way that it contains member subobjects for any non-static data members. Actually though, the example given in the question happens to be a special case: the "empty base class optimization". This base class subobject is permitted to be size zero, even though complete objects of type base are never size zero.

This containment is a low-level thing, though. It's true as others say that conceptually bases are different from members, and the syntax and semantics of the language treat them differently even though the sub-objects themselves are all just parts of the layout of the class.

3) This is an implementation detail. The code in the body of the base class constructor is executed before the code in the body of the derived class constructor, and in effect the derived class constructor is then executed in an invisible compiler-generated try/catch block to ensure that if it throws, the base class is destructed. But it's up to the compiler how to achieve this in terms of what the function entry points in the emitted code actually do.

When a class has virtual bases it's common for a constructor to result in two different function bodies being emitted - one for use when this class is the most derived type, and one for use when this class is itself a base class. The reason is that virtual base classes are constructed by the most-derived class, to ensure that when they're shared they're only constructed once. So the first version of the constructor will call all base class constructors, whereas the second version will only call constructors for non-virtual bases.

The compiler always "knows" what bases the class has, because you can only construct an object of a complete type, which implies the compiler can see the class definition, and that specifies the bases. So there's no question of only "finding that it has a base class" when the constructor is entered - the compiler knows that it has a base class, and if the call to the base class constructor is located inside the derived class constructor code, that's just for the convenience of the compiler. It could emit the calls to the base class constructors at every place you construct an object, and for that matter in cases where the derived class constructor can be and is inlined, that's the final effect.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • +1, on 3), while the exact symbols being emitted are an implementation detail, the compiler must start constructing from the most derived type, as that is the generated piece of code that has the proper arguments to pass to the base constructor, so the execution must start there, to jump over to the base classes and then continue down the hierarchy. – David Rodríguez - dribeas Dec 07 '11 at 22:53
  • @David: yes, good way of putting it, there could be considerable code in the initializer list before you even get to the body of the derived class constructor. This is why constructors don't have a name and you can't take their address -- to leave the implementation free to shove whatever code it likes into the start of the function, before the constructor body, safe in the knowledge that it cannot (legally) be called directly. – Steve Jessop Dec 08 '11 at 09:48
  • @SteveJessop How one can extract this embedded base class object from derived object? Or it's address? – ziemowit141 Aug 27 '20 at 13:53
  • @ziemowit141: `dynamic_cast` the derived object pointer for a virtual base, `static_cast` is sufficient for a non-virtual base. – Steve Jessop Jan 25 '21 at 18:07
2
  1. Yes

  2. Well, conceptually, not really. d1obj contains all of the data members that an instance of base would, and responds to all of the member functions that said instance would, but does not "contain" an instance of base: you cannot say d1obj.base.func() for instance. If you have, say, overloaded a method your parent declared, you can, however, call d1obj.base::func() to get its implementation, rather than just calling d1obj->func().

While it may seem a little like splitting hairs to say that you contain all of the data and methods of your parent without conceptually containing an instance of your parent, this is an important distinction, as you can often attain many of the benefits of direct inheritance by creating a class which contains the "parent" class as member data, like so:

class derived2 /*no parent listed */ {
public:

   derived2() :_b() {}

private:
    base _b;
}

Such a construct allows you to make use of methods already implemented by base, as implementation details of your own methods, without having to expose any methods which base declared public but that you don't want to grant access to. An important example of this is stack, which can contain an instance of another STL container (such as vector, deque or list), then use their back() to implement top(), push_back() for push() and pop_back() for pop(), all without having to expose the original methods to the user.

matthias
  • 2,419
  • 1
  • 18
  • 27
  • 1
    *you cannot say d1obj.base.func() for instance if you have overloaded a method your parent declared*. Yes, you can, but the syntax is slightly different: `d1obj.base::func()` will call the `base` member function `func` even if it was *hidden* or *overridden* in the derived type (*overloaded* means that a *different* signature for `func` is defined in the derived type, and that would *hide* the parent version) – David Rodríguez - dribeas Dec 07 '11 at 22:45
  • My point was in the syntax. In the example I gave, you're attempting to access a contained object of some sort. In your example, similar to the second example I gave, you're asking `d1obj` to behave like its parent for that one call. It seems like a small difference, but `derived1::func()` may modify the same members that `base::func()` would have, only in a different way, so it really is a behavior shift, and not just modifying a contained base class. You *inherited* individually data members from `base`, but they're not wrapped together in a `base` struct internally. – matthias Dec 07 '11 at 22:50
  • 1
    I don't quite know where you get your rationales, but *"You inherit individually data members from base, but they're not wrapped together in a base internally"* is again imprecise, the derived type does contain a base type subobject wrapped together as a `base`. Consider what happens when you upcast or when a member function that is not overiden is executed, it must be able to locate the base members with the exact distribution in memory regardless of whether the final object is of type `base` or `derived`. I agree that *conceptually* the derived type *is-a* rather than *contains*. – David Rodríguez - dribeas Dec 07 '11 at 22:59
  • It's not wrapped within memory, data members are simply laid out in the order in which they were declared, with all of `base`'s members being laid out before any of `derived1`'s. When you upcast, you grab the chunk of memory of `sizeof(base)` starting with `this`. There's no protection/intelligence going on there. And when `derived1` wants to access a member inherited from `base`, it's just `this->m`. It doesn't care whether it's an inherited member, it just knows how much it needs to offset from `this` to read an object of `m`'s type. – matthias Dec 07 '11 at 23:16
  • @mattias I agree that there is no special protection, and that the compiler will perform lookup going from the most derived object up to the first and you don't need to qualify the members (unless you have hidden them). You call that *compatible layout* and others call it *contains a base subobject*. I am one of the latter, and I believe that the standard does actually call it that, but nonetheless, how do virtual bases work then? – David Rodríguez - dribeas Dec 07 '11 at 23:19
  • 1
    From the standard, 1.8p2 *Objects can contain other objects, called subobjects. A subobject can be a member subobject (9.2), a base class subobject (Clause 10), or an array element. An object that is not a subobject of any other object is called a complete object.* The quote refers to the base class as a *subobject* of the *complete* object. – David Rodríguez - dribeas Dec 07 '11 at 23:23
  • Virtual base classes simply allow a class that has inherited `base` twice to use the same memory fields for those members no matter what inheritance-route you cite to reference the field. And yes, the standard refers to "base subobject", but that is not reflected in the language. There is no way for you to refer to such an subobject as you could any member subobject. You can cast or use specifiers to ask `d1obj` to behave like its parent, or to specify a member inherited in a certain way, but there is no `d1obj.base` member presented in the language. You *can* access inherited members though. – matthias Dec 07 '11 at 23:53
  • I guess we have to agree in disagreeing, your rationale is that since you cannot access the base as a member then there is no base object in the derived type --that is, you use *can be accessed as a member* as criterion for *contains*. On the other hand the standard (which is what defines the language) says otherwise, there is a base subobject that is not a member. The question about virtual bases is because the implementation of virtual bases is done by storing a pointer to the virtual base in each class that virtually derives, which means that there is one. – David Rodríguez - dribeas Dec 08 '11 at 13:18
  • So is this possible or not? I upvoted because it looks plausible. – mLstudent33 May 25 '20 at 08:08
1
  1. Yes. Imagine that in the constructor of the derived class, you want to use some members of the base class. Therefore, they need to be initialized. So it makes sense for the base class constructor to be called first.

  2. d1obj is a base class object. That's what inheritance is. In a way you could say it contains an object of the base class. In memory, the first part of the object will correspond to a base object (in your example you have no virtual functions, if you did, you'd have a pointer to the vftable of derived1 first, then your base class members) and after that the members belonging to derived1.

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • 3
    No, do take it literally. The derived object literally contains a base subobject. You can even get a pointer to it by static-casting a pointer to the derived object. – Kerrek SB Dec 07 '11 at 16:12
  • Just be careful as _containment_ can easily be confused as _composition_, which is a similar but different paradigm. – Chad Dec 07 '11 at 16:14
  • @KerrekSB you're right, but it depends on your interpretation of literally is. I'd find something like class B; class A { B _b }; something where 'containment' applies literally. I might be wrong. – Luchian Grigore Dec 07 '11 at 16:16
  • @LuchianGrigore: Perhaps you're confused about *syntax*, i.e. how to *name* the subobject and its members. True, the syntax differs from that for naming member objects. Nonetheless, the subobject is there. (Remember that not everything in C++ has a name, e.g. constructors.) – Kerrek SB Dec 07 '11 at 16:19
1

Yes, and yes.


Since your edit, ad 3) The derived class constructor calls the base class constructor as its first act of duty; then all the member object constructors, and finally it executes the constructor body.

Destruction works the opposite way: First the destructor body executes, then the member objects are destroyed (in reverse order of their destruction), and finally the base subobject destructor is called (which is why you always need an accessible destructor in the base class, even if it is pure-virtual).

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
0

1-- Yes. And it is logical that way.

Objects of type derived1 are special objects of type base, which means that, as a first thing, they are objects of type base. This is what is constructed first, then "derived1" adds its "specialty" to the object.

2-- It is not a question of containing, it is inheritance. See my paragraph above to better understand this answer.

Benoît
  • 16,798
  • 8
  • 46
  • 66