Почему sizeof (std:: variant) имеет тот же размер, что и структура с теми же членами?

Шаблон класса std::variant представляет объединение с безопасным типом. Экземпляр std::variant в любой момент времени либо содержит значение одного из его альтернативных типов, либо не содержит значения.

sizeof(std::variant<float, int32_t, double>) == 16

Но если это союз, почему он занимает так много места?

struct T1 {
    float a;
    int32_t b;
    double c;
};

struct T2 {
    union {
        float a;
        int32_t b;
        double c;
    };
};

Вариант имеет тот же размер, что и структура

sizeof(T1) == 16
sizeof(T2) == 8

Я ожидаю, что размер объединения плюс 4 байта для хранения, какой тип активен.

Ответ 1

Здесь тип с наибольшим выравниванием в variant является double, с выравниванием 8. Это означает, что весь variant должен иметь выравнивание 8. Это также означает, что его размер должен быть кратным 8, гарантируя, что в массиве каждый variant будет иметь double выравнивание по 8.

Но вариант должен хранить больше, чем просто самый большой тип: он также должен хранить идентификатор или тег, чтобы знать, какой тип в данный момент создается. Таким образом, даже если для этого идентификатора используется только 1 байт, структура в целом дополняется до 16, так что она кратна 8 размеру.

Более правильное сравнение будет:

struct
{
    union
    {
        float a;
        int32_t b;
        double c;
    };
    int identifier;
};

Ответ 2

Основной ответ: для выравнивания. Так как один из ваших типов является двойным, а удваивает 8 байтов с 8-байтовыми требованиями к выравниванию, это означает, что вариант также имеет требование выравнивания по 8 байт. Таким образом, он может быть только кратным 8 байтам. Как вы сами отметили, минимальный размер - это самый большой тип + что-то дополнительное, чтобы указать, какой член активен. Это означает, что он не может вписаться в 8 байтов, но тогда требования к выравниванию вынуждают его полностью до 16 байтов.

Вот почему он не может быть 12 байтов, как вы думаете. Между прочим, нет также правила, согласно которому хранилище активного типа должно быть 4 байта. На самом деле, довольно ясно, что в подавляющем большинстве случаев вы можете обойтись одним байтом, который может различать 255 типов, плюс пустое состояние. Мы можем проверить это:

struct null {};
std::cerr << sizeof(std::variant<bool, null>);

Я только что проверил это на coliru, и он напечатал "2". В этом случае 1 байт для самого большого типа (bool) и 1 байт, чтобы определить, какой тип активен.

Ответ 3

В то время как варианты логически являются типами безопасных соединений, не гарантируется, что их размеры будут равны размерам сырых объединений с одинаковыми членами.

Понятно, что, поскольку вариант (в отличие от raw union) должен хранить информацию о текущем активном типе, он должен быть больше. Фактический размер варианта будет зависеть от архитектуры (дополнения) и реализации, поскольку Standard не налагает ограничений на размер.

Ответ 4

Как ни странно, вы были обмануты совпадением. Возврат следующего: 16:

sizeof(std::variant<float, int32_t, double, int64_t>)

И это тоже:

sizeof(std::variant<float, int32_t, double, int64_t, double>)

Таким образом, в основном есть внутренняя переменная в std::variant, размер которой равен 8 байтам (или меньше, но соответствует 8 байтам). Это, в дополнение к вашему союзу, составляет 16.