Общий базовый класс ломает пустую оптимизацию базового класса для кортежей

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 байта. Есть ли способ избежать этого, не нарушая иерархию классов? Или я должен реализовать свою собственную парную реализацию, которая оптимизирует для этого случая каким-то другим способом?

3

Решение

Причина, по которой пустой объект должен занимать некоторое пространство, состоит в том, что два разных объекта должны иметь разные адреса. Исключением является то, что базовый подобъект производного типа может иметь тот же адрес, что и производный завершенный объект (если первый нестатический член производного типа не имеет тот же тип, что и базовый [*]. Оптимизация пустой базы использует это для удаления дополнительного пространства, которое произвольно добавляется к пустой базе, чтобы гарантировать, что 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 объекты, и они должны иметь разные адреса.

6

Другие решения

Эта оптимизация может быть сложно осуществить, как вы можете легко увидеть в своем эксперименте. Я считаю эту оптимизацию наиболее полезной, когда 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 оптимизируется.

2

По вопросам рекламы [email protected]