Should an empty base class affect the layout of the derived class?
Asked Answered
D

4

5

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?

Doan answered 13/10, 2011 at 20:43 Comment(6)
Which version of gcc? gcc 4.7 outputs 1 here when member is set to char.Conchaconchie
@envu, a version that is specified by another SDK.Doan
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).Affinal
I think Bo Persson is right. As far as I see, VC's behaviour is not conforming. N3290 10/8 and C++03 10/7 say: two subobjects that have the same class type and that belong to the same most derived object must not be allocated at the same addressSilage
@BoPersson, can you make that an answer? Given the wording of 10/8 I think that's the most appropriate interpretation.Doan
@Doan - Ok, I have expanded the comment a bit as an answer.Affinal
A
5

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.

Affinal answered 14/10, 2011 at 16:32 Comment(1)
By the way, the standard prohibits standard-layout classes to have a base class of the same type as the first member, presumably for precisely this reason.Eatmon
A
4

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.

Alforja answered 13/10, 2011 at 20:45 Comment(0)
R
3

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.

Ruderal answered 13/10, 2011 at 21:15 Comment(1)
A related question would be in my example, would: derived d; printf("%d", static_cast<empty *>(&d)==static_cast<empty *>(&d.m)); print 1 or 0? That determines whether the size is 1 or >1.Doan
F
1

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.

Fachan answered 14/10, 2011 at 17:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.