Какова цель использования союза только с одним членом?

89

Когда я читал исходный код seastar , я заметил, что существует структура объединения, tx_sideкоторая имеет только один член. Это какой-то хак для решения определенной проблемы?

К вашему сведению, я вставляю tx_sideструктуру ниже:

union tx_side {
    tx_side() {}
    ~tx_side() {}
    void init() { new (&a) aa; }
    struct aa {
        std::deque<work_item*> pending_fifo;
    } a;
} _tx;
daoliker
источник
1
Потенциальный дубликат stackoverflow.com/questions/26572432/… .
Макс
5
@MaxLanghof Этот вопрос и соответствующие ответы не упоминали о цели использования такой структуры объединения.
daoliker
У вас есть пример использования этого члена?
n314159
4
Вот почему я на самом деле не использовал свой закрывающий голос. Но я не уверен, что именно вы ожидаете от ответов на ваш вопрос, что не следует непосредственно из ответов там. Предположительно, цель использования unionвместо structодного или нескольких различий между ними. Это довольно непонятная методика, поэтому, если не появится оригинальный автор этого кода, я не уверен, что кто-то может дать вам авторитетный ответ, какую проблему они надеются решить с этим (если есть).
Макс
2
Мое лучшее предположение состоит в том, что объединение используется либо для задержки построения (что в данном случае несколько бессмысленно), либо для предотвращения разрушения (что приводит к утечке памяти) pending_fifo. Но трудно сказать без примера использования.
Константин Ступник

Ответы:

82

Потому tx_sideчто это объединение, которое tx_side()не инициализирует / не создает автоматически aи ~tx_side()не разрушает его автоматически. Это позволяет детально контролировать время жизни aи pending_fifo, с помощью размещения новых и ручных вызовов деструкторов (бедняков std::optional).

Вот пример:

#include <iostream>

struct A
{
    A() {std::cout << "A()\n";}
    ~A() {std::cout << "~A()\n";}
};

union B
{
    A a;
    B() {}
    ~B() {}
};

int main()
{
    B b;
}

Здесь B b;ничего не печатается, потому что aне построено и не разрушено.

Если Bбыл struct, B()назвал бы A()и ~B()назвали бы ~A(), и вы не смогли бы предотвратить это.

HolyBlackCat
источник
23
@daoliker не обязательно случайный, но непредсказуемый вами. То же, что и любая другая неинициализированная переменная. Вы не можете предположить, что это случайно; все, что вы знаете, может содержать пароль пользователя, который вы ранее просили его ввести.
user253751
5
@daoliker: предыдущий комментарий слишком оптимистичен. Случайные байты будут иметь значения в диапазоне 0-255, но если вы прочитаете неинициализированный байт в, intвы можете получить 0xCCCCCCCC. Чтение неинициализированных данных является неопределенным поведением, и может случиться так, что компилятор просто отбрасывает попытку. Это не просто теория. Debian сделал именно эту ошибку, и это нарушило их реализацию OpenSSL. У них было несколько реальных случайных байтов, они добавили неинициализированную переменную, и компилятор сказал: «Ну, результат не определен, поэтому он также может быть нулевым». Ноль, очевидно, уже не случайный.
MSalters
1
@MSalters: у вас есть источник для этого заявления? Потому что то, что я могу найти, говорит о том, что это не то, что произошло: не компилятор удалил его, а разработчики. Честно говоря, я был бы удивлен, если бы любой автор компилятора принял такое невероятно плохое решение. (см. stackoverflow.com/questions/45395435/… )
Джек Эйдли
5
@JackAidley: Какая точная претензия? У вас есть хорошая ссылка, кажется, я перевернул историю. OpenSSL неправильно понял логику и использовал неинициализированную переменную таким образом, чтобы компилятор мог на законных основаниях предполагать любой результат. Debian правильно заметил это, но сломал исправление. Что касается «компиляторов, принимающих такие плохие решения»; они не принимают это решение. Неопределенное поведение - плохое решение. Оптимизаторы предназначены для работы с правильным кодом. GCC, например, активно не предполагает переполнения со знаком. Предполагается, что «нет неинициализированных данных» одинаково разумно; это может быть использовано для устранения невозможных путей кода.
MSalters
1
@JackAidley Я столкнулся с похожими проблемами, о которых @MSalters упоминает в своем собственном коде; Я ошибочно полагал, что неинициализированная переменная будет пустой, и был озадачен, когда последующее != 0сравнение дало истину. С тех пор я добавил флаги компилятора для обработки неинициализированных переменных как ошибок, чтобы избежать повторного попадания в эту ловушку.
Том Линт
0

Проще говоря, если явно не назначено / не инициализировано значение, объединение с одним членом не инициализирует выделенную память. Эта функциональность может быть достигнута std:: optionalв C ++ 17.

Sitesh
источник
2
Это вводящий в заблуждение ответ. Союз только с одним членом будет иметь тот же размер, что и член. Эта память просто не будет инициализирована до инициализации члена.
Кирилл Дмитренко