Is base class object created when a derived class object is created?

When i create an object of derived class,
Does the base class object gets created too?
Meaning there are 2 objects? Because i have to use base class constructor to initalize the data member first.
> When i create an object of derived class, does the base class object gets created too?

Yes.

Each direct and indirect base class is present, as base class subobject, within the object representation of the derived class at ABI-dependent offset. Empty base classes usually do not increase the size of the derived object due to empty base optimization. The constructors of base class subobjects are called by the constructor of the derived class: arguments may be provided to those constructors in the member initializer list.
https://en.cppreference.com/w/cpp/language/derived_class


> Meaning there are 2 objects?

The anonymous base class object is a subobject of the derived class object;
it forms part of the object representation of the derived class object.

This may be illustrative:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>

template < int N > struct A
{
    A() : A(0) {}
    A( int v ) : value(v) { std::cout << "A<" << N << ">::constructor, v == " << v << ", object at " << this << '\n' ; }

    int value ;
};

int main()
{
    struct B : A<0>, A<1> // two base classes
    {
        B() { std::cout << "B::constructor, object at " << this << '\n' ; }
        B( int v ) : A<0>(v), A<1>(v+1), a2(v+2), a3(v+3) { std::cout << "B::constructor, object at " << this << '\n' ; }

        // two member objects
        A<2> a2 ;
        A<3> a3 ;

        void debug_dump() const noexcept
        {
            std::cout << "\ndump of object of type B\n------------------\n"
                      << "base class object of type A<0> at " << static_cast< const A<0>* >(this) << '\n'
                      << "base class object of type A<1> at " << static_cast< const A<1>* >(this) << '\n'
                      << "member object of type A<2> at " << std::addressof(a2) << '\n'
                      << "member object of type A<3> at " << std::addressof(a3) << '\n'
                      << "object of type B at " << this << '\n' ;
        }
    };

    const B bb ; // subobjects are default initialised
    bb.debug_dump() ;

    std::cout << "\n\n" ;

    const B bb2(123) ; // a member initializer list is used to initialise the subobjects
    bb2.debug_dump() ;
}

http://coliru.stacked-crooked.com/a/8cd4e52cf54bb2a0
You can think of a derived class as a class that "copies" (inherits) the definition of its base class and extends that definition. The derived class has an inheritance relationship to its base class, but it still is a class of its own. That's different from, e.g. JavaScript, where objects do contain a reference to their "prototype" object.

An instance (object) of the derived class "contains" everything that an instance of the base class would have contained, plus everything that the derived class added. So, there is a sort of "sub-object" representing the base class within the object of the derived class. Also, the base class' constructor is invoked (before the derived class' constructor) when an object of the derived class is created. But there are no two separate objects!

Consider the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class A
{
public:
    A(const uint64_t a, const uint64_t b, const uint64_t c)
    {
        this->a = a;
        this->b = b;
        this->c = c;
    }
private:
    uint64_t a, b, c;
};

class B : public A
{
public:
    B(const uint64_t a, const uint64_t b, const uint64_t c, const uint64_t x, const uint64_t y, const uint64_t z)
    :
        A(a, b, c)
    {
        this->x = x;
        this->y = y;
        this->z = z;
    }
private:
    uint64_t x, y, z;
};

int main()
{
    A* ptr_a = new A(1, 2, 3);
    printf("size of A = %zu\n", _msize(ptr_a));

    B* ptr_b = new B(1, 2, 3, 4, 5, 6);
    printf("size of A = %zu\n", _msize(ptr_b));
}

Output:
size of A = 24
size of B = 48

We can see that an object of the derived class B has twice the size of an object of the base class A. That is because an object of class B needs to contain the three fields inherited from class A (which are 8 bytes in size each, so 24 bytes overall) plus the three additional fields added by class B (again 24 bytes).

If, there really were two separate objects in memory, and the B-object contained an implicit pointer to a "hidden" A-object, then this would look different! A pointer is only 4 or 8 bytes in size, so the B-object would then be 28 or 32 bytes in size, but certainly not 48 bytes. Also, how exactly would the memory block for the "hidden" A-object be allocated, and how could we ever free that memory to avoid memory leaks?

Long story short, in reality there are no two separate objects in memory, only one "combined" object.
Last edited on
A subobject is an object; its lifetime is governed by the lifetime of its complete object.

Objects can contain other objects, called subobjects. A subobject can be a member subobject, a base class subobject, or an array element. An object that is not a subobject of any other object is called a complete object.
https://eel.is/c++draft/intro.object#2

For every object x, there is some object called the complete object of x, determined as follows:
-- If x is a complete object, then the complete object of x is itself.
-- Otherwise, the complete object of x is the complete object of the (unique) object that contains x.
https://eel.is/c++draft/intro.object#5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <string>

struct A { std::string what ; };

void foo( A& a ) // got reference to an object of type A
{
    // the object referred to may be a complete object or a subobject
    //      (a baseclass subobject, a member subobject or an array element subobject)
    // within this function, we have no standard way of (and no real need for) distinguishing between them.
    a.what += '!' ; // a.what is a subobject of type std::string
    std::cout << a.what << '\n' ;
}

int main()
{
    A a{ "complete object" } ;
    struct B : A { B() : A{"base class subobject" } {} } b;
    struct C { A a ; C() : a{"member subobject" } {} } c;
    A array[] { A{ "array element subobject (#0)" }, A{ "array element subobject (#1)" }, A{ "array element subobject (#2)" } } ;

    foo(a) ; // complete object!
    foo(b) ; // base class subobject!
    foo(c.a) ; // member subobject!
    foo( array[1] ) ; // array element subobject (#1)!
}

http://coliru.stacked-crooked.com/a/49dc663fd29f3445
A subobject is an object; its lifetime is governed by the lifetime of its complete object.

I think that's a valid way of looking at this, from a pure "theoretical" point of view.

Technically, there is just one object, containing the fields of the derived class as well as the fields of the base class, as has been demonstrated above. That is fundamentally different from prototype-based programming, where objects are created by "cloning" an existing object and the object still has a pointer to its "prototype" (base) object, so that any changes to the "prototype" object effect all objects cloned from that prototype.

Another proof that there's just one object in C++, in an actual technical sense: Fields that have been inherited from the base class can be accessed via the this pointer like this->some_field. If, in an actual technical sense, there was a separate object for the base class and the derived object contained a pointer to that "base" object, then we would have to use sth. like this->base_object.some_field. But that is not the case.

I think we can say: Even though there is a certain section in the derived class object, that, if it were "cut out" of that object, would resemble a valid base class object, this section (sub-object) is in fact "merged" into the derived class object, not linked to it as a separate object. I think that is what the original question was about?
Last edited on
> Technically, there is just one object, containing the fields of the derived class as well as the fields of the base class,

Technically, there is an object which contains a base class subobject and the fields of the derived class. The base class subobject contains the member objects of the base class.


> Fields that have been inherited from the base class can be accessed via the this pointer like this->some_inherited_field.

That is how the member-name-lookup rules of the language are framed.

The lookup set for N in C, called S(N,C), consists of two component sets: the declaration set, a set of members named N; and the subobject set, a set of subobjects where declarations of these members were found (possibly via using-declarations).
https://eel.is/c++draft/class.member.lookup#def:lookup_set

Otherwise (i.e., C does not contain a declaration of N or the resulting declaration set is empty), S(N,C) is initially empty. Calculate the lookup set for N in each direct non-dependent base class subobject Bi, and merge each such lookup set S(N,Bi) in turn into S(N,C).
https://eel.is/c++draft/class.member.lookup#4

It is trivially easy to verify that members of the base class do not become members of the derived class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <string>

struct A { int i = 0 ; };
struct B { int i = 99 ; };
struct C { private: int i = -52 ; };

struct D : A
{
    D() : i(5) {} // *** error *** : i is not a member of D
};

struct E : C
{
    E() { this->i = 23 ; } // *** error *** : can't access private member of C
};

struct F : A, B, C
{
    // F does not have three members with the same name i
    F() { this->i = 23 ; } // *** error *** : ambiguous; i is a member of multiple base classes
};

struct G : A
{
    std::string i = 345.67 ; // this is a member of G
    G() : i( "hello" ) {} // fine: refers to the std::string member of G
    G( int ) {this->i = 23 ; } // *** error *** : no implicit conversion from int to std::string
};
Topic archived. No new replies allowed.