i have a question about the sizeof an instance of a class that uses CRTP to implement an interface, as the following example.
#include <iostream>
class Interface
{
public:
virtual int foo() {
std::cout << "Interface\n";
return 0;
};
};
template <class Derived>
class Base : Interface
{
public:
virtual int foo() override {
Interface::foo();
std::cout << "base\n";
return static_cast<Derived*>(this)->bar2;
}
int bar;
};
class Derived : public Base<Derived>
{
public:
virtual int foo() override {
std::cout << "Derived\n";
Base<Derived>::foo();
return bar;
}
int bar2;
};
int main()
{
std::cout << sizeof(Base<Derived>) << '\n';
std::cout << sizeof(Derived) << '\n';
return 0;
}
both gcc and clang output 16 as both the sizeof(Derived) and sizeof(Base<Derived>), same result if i remove Base and implement it all in Derived as if they are all the same object godbolt example
the question is, why does this happen ? does the compiler simply combine both Base<Derived> and Derived into a single class when it sees CRTP being used ? or am i missing something ? how does the compiler get away from storing the extra pointers to the vtables in Derived and shrink its size to have only 1 pointer size overhead ? and is there any hidden overhead with this approach ?
i was expecting that the compiler would put a pointer to vtbl of Derived then the int then a pointer vtbl of Base then an int then a pointer to the vtbl of the interface in Derived bringing its size to 40 bytes, but this doesn't seem to be the case.
If you print out the offsets of the data members
barandbar2, it becomes obvious what's going on:This prints:
Judging by this, the layouts of the classes look like:
Note:
vptris the pointer to the vtable of the object.Remember that there is only one vtable pointer per polymorphic base class. When the derived class is initialized, it replaces the
vptrin the base class subobject. See also: Where in memory is vtable stored?The last four bytes of
Base<Derived>are padding, andDerivedputs itsbar2member into that padding. This is allowed becauseBase<Derived>is a polymorphic class, and thus not standard-layout. As a result, even thoughDerivedhas one extra data member compared toBase<Derived>it does not take up any more space.If you added another
int bar3member, it should take up 24 bytes.