gcc 4.7.1 выполняет пустую оптимизацию базового класса для кортежей, что я считаю действительно полезной функцией. Тем не менее, кажется, что это неожиданное ограничение:
#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.");
В этом случае последнее утверждение не выполняется, так как на самом деле кортеж превышает 4 байта. Есть ли способ избежать этого, не нарушая иерархию классов? Или я должен реализовать свою собственную парную реализацию, которая оптимизирует для этого случая каким-то другим способом?
Причина, по которой пустой объект должен занимать некоторое пространство, состоит в том, что два разных объекта должны иметь разные адреса. Исключением является то, что базовый подобъект производного типа может иметь тот же адрес, что и производный завершенный объект (если первый нестатический член производного типа не имеет тот же тип, что и базовый [*]. Оптимизация пустой базы использует это для удаления дополнительного пространства, которое произвольно добавляется к пустой базе, чтобы гарантировать, что sizeof x!=0
для любого завершенного объекта.
В вашем случае кортеж держит два A
подобъекты, один является основой C
другой является основой B
, но они разные и как таковые они должны иметь разные адреса. Ни один из этих двух объектов не является базовым подобъектом другого, поэтому они не могут иметь один и тот же адрес. Вам даже не нужно использовать std::tuple
чтобы увидеть этот эффект, просто создайте другой тип:
struct D : B, C {};
Размер D
будет строго больше, чем размер обоих B
а также C
, Чтобы проверить, что на самом деле есть два A
подобъекты, вы можете попытаться выгрузить указатель на A
и компилятор с удовольствием выложит какую-то ошибку в вашу сторону.
[*] Стандарт явно запрещает этот случай также по той же причине:
struct A {};
struct B : A { A a; };
Опять же, в этом случае в каждом полном объекте типа B
, есть два A
объекты, и они должны иметь разные адреса.
Эта оптимизация может быть сложно осуществить, как вы можете легко увидеть в своем эксперименте. Я считаю эту оптимизацию наиболее полезной, когда tuple
является членом данных другого класса (особенно, если я предполагаю поместить этот класс в контейнер). Есть ли другие элементы данных, которые вы можете связать в этом tuple
без разоблачения этого факта? Например:
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_);}
};
Для меня это никоим образом не гарантировано, std::tuple<B, int, C, int>
только 12 байтов, и так C
оптимизируется.