16

Why is the empty base class optimization (EBO) not being fully applied in Visual C++?

If I have a lot of base classes, is there any way for me to help the compiler make this optimization?

#include <iostream>

struct T1 { };
struct T2 { };
struct T3 { };
struct T4 { };
struct T5 { };
struct T6 { };

struct Test : T1, T2, T3, T4, T5, T6 { };

int main() { std::cout << sizeof(Test); }   // Prints 5
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
user541686
  • 205,094
  • 128
  • 528
  • 886
  • 1
    Are you compiling in debug mode? – jpm Oct 03 '12 at 03:28
  • @jpm: Nope, it's in release mode. – user541686 Oct 03 '12 at 03:32
  • Any optimization that affects memory layout would need to be done consistently across all compilation units. So this would have to be either mandatory, or not at all - in a similar way to why the compiler cannot re-order struct/class members. – Mysticial Oct 03 '12 at 03:33
  • @Mysticial: I don't understand what you mean. It's still optimizing, from 7 classes down to 5 -- just not any more than that. How does that work? – user541686 Oct 03 '12 at 03:35
  • 2
    Hm... I didn't see that you had 6 of them... That makes it even more messed up... – Mysticial Oct 03 '12 at 03:36
  • 1
    @Mysticial: Yeah, it's 7, if you count the derived class as well. That's why it's confusing me lol. (Funny thing is, it doesn't seem to go away when you nest them either. I had a bunch of empty classes with 1-2 base classes each, but they all inherited from each other, and the objects ended up being like 30 bytes!!) – user541686 Oct 03 '12 at 03:36
  • That aside. About the consistency thing. If one compilation unit has the size at 7, but another unit is optimized to something less, then they would be incompatible. (imagine passing a pointer from one to the other) That's why they need to be consistent. – Mysticial Oct 03 '12 at 03:38
  • @Mysticial: Ah yeah definitely. – user541686 Oct 03 '12 at 03:39
  • 3
    [g++ is working fine](http://ideone.com/9PHX1), may be this is MS specific behavior or debug flags have to be set. – iammilind Oct 03 '12 at 03:43
  • We're talking about it in the [lounge](http://chat.stackoverflow.com/rooms/10/loungec) right now - if you wanna join in. – Mysticial Oct 03 '12 at 03:47
  • Chained inheritence (`T2 : T1, T3 : T2 ... T6 : T5, Test : T6`) makes it `1`, same with `Test : T5, T6`. However, `Test : T4, T5, T6` and having `T5` and `T6` not inherit from anything prints `2`. I'm confuzzled at MSVC's optimization pass. :/ – Xeo Oct 03 '12 at 03:49
  • 1
    Perhaps it is applying the optimization but only to one of the base classes. That would reduce the size from 6 down to 5. @Xeo this appears to be consistent with your results as well. – Mark Ransom Oct 03 '12 at 03:50
  • @MarkRansom Join us in the [C++ chatroom](http://chat.stackoverflow.com/rooms/10/loungec). We're having a pretty good discussion of it. – Mysticial Oct 03 '12 at 03:55
  • @Mark: Nvm, I miscalculated there. :) – Xeo Oct 03 '12 at 03:57
  • 5
    This behaviour is the reason boost operators has base class chaining instead of simply letting the user do multiple inheritance http://www.boost.org/doc/libs/1_51_0/libs/utility/operators.htm#chaining – R. Martinho Fernandes Oct 03 '12 at 04:15

3 Answers3

18

This is a longstanding bug in the Visual C++ compiler. When a class derives from multiple empty base classes, only the initial empty base class will be optimized using the empty base optimization (EBO).

This issue was reported on Microsoft Connect in 2006: Empty Base Optimization Not Working Properly. At the moment, old bugs are not visible on Microsoft Connect. I am told that this is a temporary issue, though I do not know when it will be resolved. In the meantime, the following is the response to the bug from Jonathan Caves, who is one of the developers on the Visual C++ compiler team:

Hi: unfortunately even though this is a bug in the Visual C++ object model we are unable to fix it at this time given that fixing it would potentially break a lot of existing programs as the sizes of objects would change. Hopefully in the future we may be able to address this issue but not for the next release of the product.

Thanks for reporting the issue.

Community
  • 1
  • 1
James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • I always find it interesting how MSDN links break right when you *really* need them, at least they plan on fixing it... – Necrolis Oct 03 '12 at 20:04
  • 2
    This is still an issue in VS2017. However, it can be circumvented with some manual work according to this [blog post](https://blogs.msdn.microsoft.com/vcblog/2016/03/30/optimizing-the-layout-of-empty-base-classes-in-vs2015-update-2-3/). Since VS2015 Update 2 it is possible to add `__declspec(empty_bases)` to the derived class declaration to enforce the application of EBO in multiple inheritance scenarios. This has to be done manually to maintain binary compatibility. – w1th0utnam3 Dec 02 '17 at 21:45
9

Since Visual Studio 2017 Update 2, there is a fix for this... but is it disabled by default. And you have to explicitly enable it for every class separately:

    struct __declspec(empty_bases) Test : T1, T2, T3, T4, T5, T6 { };
    //     ^^^^^^^^^^^^^^^^^^^^^^^

    static_assert(1 == sizeof(Test));

Sadly, this still holds true even for /std:c++latest and /permissive- even in Visual Studio 2019: There is no way of setting it globally.

Tobi
  • 2,591
  • 15
  • 34
8

The 'official' stance is MSVC will only do EBO for single inheritance, unfortunately the bug report where this is stated was deleted by MS, so all that remains is an older question on MSDN that points it out and references the now deleted bug report.

Necrolis
  • 25,836
  • 3
  • 63
  • 101