Common base class breaks empty base class optimization for tuples

痴心易碎 提交于 2019-12-23 18:12:35

问题


gcc 4.7.1 does empty base class optimization for tuples, which I consider a really useful feature. However, there appears to be an unexpected limit to this:

#include <tuple>
#include <cstdint>
#include <type_traits>
class A { };
class B : public A { std::uint32_t v_; };
class C : public A { };
static_assert(sizeof(B) == 4,                "A has 32 bits.");
static_assert(std::is_empty<C>::value,       "B is empty.");
static_assert(sizeof(std::tuple<B, C>) == 4, "C should be 32 bits.");

In this case, the last assertion fails, as the tuple is in fact larger than 4 bytes. Is there a way to avoid this, without breaking the class hierarchy? Or do I have to implement my own pair implementation which optimizes for this case in some other way?


回答1:


The reason that an empty object must take some space is that two different objects must have different addresses. The exception to that is that the base subobject of a derived type can have the same address as the derived complete object (if the first non-static member of the derived type is not of the same type as the base [*]. The empty base optimization uses that to remove the additional space that is added to an empty base arbitrarily to ensure that sizeof x!=0 for any complete object.

In your case, the tuple is holding two A subobjects, one is the base of C the other is the base of B, but they are different and as such they must have different addresses. None of those two objects is the base subobject of the other, so they cannot have the same address. You don't even need to use std::tuple to see this effect, just create another type:

struct D : B, C {};

The size of D will be strictly greater than the size of both B and C. To check that there are in fact two A subobjects, you can try to upcast to a pointer to A and the compiler will gladly spit some ambiguity error in your direction.

[*] The standard explicitly forbids this case also, for the same reason:

struct A {};
struct B : A { A a; };

Again, in this case in each complete object of type B, there are two A objects, and they must have different addresses.




回答2:


This optimization can be tricky to pull off, as you can easily see with your experiment. I find this optimization most useful when the tuple is a data member of another class (especially if I anticipate putting that class in a container). Are there other data members that you can bundle in this tuple without exposing that fact? For example:

class D
{
    std::tuple<B, int, C, int> data_;
public:
    B& get_B() {return std::get<0>(data_);}
    C& get_C() {return std::get<2>(data_);}
    int& get_size() {return std::get<1>(data_);}
    int& get_age() {return std::get<3>(data_);}
};

For me, and this is by no means guaranteed, std::tuple<B, int, C, int> is only 12 bytes, and so C is getting optimized away.



来源:https://stackoverflow.com/questions/15076961/common-base-class-breaks-empty-base-class-optimization-for-tuples

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!