Should an empty base class affect the layout of the derived class?

500 views Asked by At

The C++ standard (quoting from draft n3242) says the following about subobjects [intro.object]:

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 distinct objects that are neither bit-fields nor base class subobjects of zero size shall have distinct addresses.

Now, given the following snippet:

struct empty { };
struct member: empty { };
struct derived: empty { member m; };

int main(void)
{
    printf("%d", sizeof(derived));
    return 0;
}

gcc I believe prints out 2, and Visual C++ 2010 prints out 1. I suspect gcc is taking the standard to mean you can't alias the storage of types if they represent different objects. And I bet MSVC is taking the standard to mean if one subobject is zero sized, you can do whatever you want.

Is this unspecified behavior?

4

There are 4 answers

1
Bo Persson On BEST ANSWER

Expanding on my earlier comment:

An object is identified by its address. If you compare the addresses (like pointers) of two objects of the same type and they compare equal, the pointers are considerd to point to the same object.

Objects of different types cannot be compared directly this way, so they are allowed to have the same address. One example is a struct and its first member. They cannot be of the same type. Neither can a base class and a derived class, so they could possibly have the same address if the base class is empty.

However, a base class and the first member of the derived class can be of the same type. This is not a problem unless the base class is also empty and the compiler tries the empty base class optimization. In that case we could have pointers to two different objects of the same type compare equal, and therefore believe that they were the same object.

So, if the members have different types (empty and char) they can have the same address. If they are of the same type they cannot, because that would break tests for object identity like if (this != &that), sometimes used to test for things like self assignment.

By the way, Microsoft agrees that this is a bug in their compiler but have other, more urgent, things to fix first.

1
Nemo On

In the final version of the C++11 standard, that paragraph was revised to read:

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.

Although I am not sure I understand what this has to do with the sizes of the objects.

0
Ben Voigt On

This is implementation-dependent.

The standard explicitly allows the Empty Base Optimization, but does not require it. In fact, the standard doesn't require much of anything about the layout of classes in memory, only that certain classes will be layout-compatible with each other (but not what the common layout is). Order of members is also specified (when there is no intervening accessibility specifier), but padding, headers, footers, and all manner of weirder stuff is allowed.

0
Maxim Egorushkin On

There are good explanations in this thread. I just wanted to add that to work around this structure bloat issue you can simply make empty class a template, so that instantiating it with a different template argument makes it a different class:

template<class T>
struct empty { };
struct member: empty<member> { };
struct derived: empty<derived> { member m; };

int main(void)
{
    printf("%d\n", sizeof(derived));
    return 0;
}

Outputs 1.

This is the reason to avoid using boost::noncopyable in large projects.