orlp
orlp

Reputation: 117701

Why does inheriting not increase the size, but a member does?

Take a look at these three classes (includes ommited):

template<class T> struct A {
    std::allocator<T> allocator;
    T* ptr;
};

template<class T> struct B {
    T* ptr;
    std::allocator<T> allocator;
};

template<class T> struct C : std::allocator<T> {
    T* ptr;
};

int main(int argc, char **argv) {
    std::cout << "A: " << sizeof(A<int>) << "\n";
    std::cout << "B: " << sizeof(B<int>) << "\n";
    std::cout << "C: " << sizeof(C<int>) << "\n";
}

If you'd ask me, depending on alignment, either A or B has to have the same size as C. However, printing the sizeof claims otherwise:

A: 16
B: 16
C: 8

Why is this so?

Upvotes: 1

Views: 94

Answers (1)

Kerrek SB
Kerrek SB

Reputation: 477100

By C++11, 9/4:

Complete objects and member subobjects of class type shall have nonzero size.

No such restriction applies to base classes, as long as every object has a unique pair of type and address. So as long as the "first" data member hasn't got the same type as a base subobject, the base subobject may have zero size, being neither complete nor a member.

(I put "first" in quotation marks since there's a complication involving access levels.)

In fact, 1.8/5–6 formalize the above:

5 Unless it is a bit-field (9.6), a most derived object shall have a non-zero size and shall occupy one or more bytes of storage. Base class subobjects may have zero size. An object of trivially copyable or standard-layout type (3.9) shall occupy contiguous bytes of storage.

6 Unless an object is a bit-field or a base class subobject of zero size, the address of that object is the address of the first byte it occupies. Two objects that are not bit-fields may have the same address if one is a subobject of the other, or if at least one is a base class subobject of zero size and they are of different types; otherwise, they shall have distinct addresses.


Here's a "typical" implementation of std::vector, minus all the name mangling:

template <typename T, typename Alloc>
class vector
{
    struct vbase : Alloc
    {
        T * data, * end, * capacity;
        vbase(Alloc const & a) : Alloc(a), data(), end(), capacity() { }
    };

    vbase impl;

public:
    vector(Alloc const & a = Alloc()) : impl(a) { }

    T * data() const { return impl.data; }
    T & operator[](size_t n) { return data()[n]; }

    // ...

    // use impl.allocate(), impl.construct() etc.
};

This basically ensures that sizeof(vector<T>) == 3 * sizeof(T*) whenever the allocator is empty.

Upvotes: 5

Related Questions