std::Optional реализован как union vs char[]/aligned_storage

Читая реализацию GCC std::optional, я заметил кое-что интересное. Я знаю, что boost::optional реализовано следующим образом:

template <typename T>
class optional {
    // ...
private:
    bool has_value_;
    aligned_storage<T, /* ... */> storage_;
}

Но тогда и libstdc++, и libc++Abseil) реализуют свои optional типы следующим образом:

template <typename T>
class optional {
    // ...
private:
    struct empty_byte {};
    union {
        empty_byte empty_;
        T value_;
    };
    bool has_value_;
}

Мне они кажутся функционально идентичными, но есть ли преимущества использования одного над другим? (За исключением очевидного отсутствия нового места размещения в последнем, что действительно приятно.)


person Ron    schedule 14.09.2018    source источник


Ответы (2)


Мне они кажутся функционально идентичными, но есть ли преимущества использования одного над другим? (За исключением очевидного отсутствия нового размещения в последнем, что действительно приятно.)

Это не просто «очень приятно» — это важно для действительно важной части функциональности, а именно:

constexpr std::optional<int> o(42);

Есть несколько вещей, которые вы не можете сделать в константном выражении, в том числе new и reinterpret_cast. Если бы вы реализовали optional с aligned_storage, вам нужно было бы использовать new для создания объекта и reinterpret_cast для его возврата, что помешало бы optional быть дружественным к constexpr.

С union реализацией у вас нет этой проблемы, поэтому вы можете использовать optional в constexpr программировании (даже до того, как исправление тривиальной возможности копирования, о котором говорит Николь о, optional уже требовалось для использования как constexpr).

person Barry    schedule 14.09.2018

std::optional нельзя реализовать как выровненное хранилище из-за исправления дефекта после C++17. В частности, std::optional<T> должен быть тривиально копируемым, если T тривиально копируем. union{empty; T t}; удовлетворит этому требованию

Внутренняя память и размещение-new/delete использование не может. Выполнение побайтовой копии объекта TriviallyCopyable в хранилище, которое еще не содержит объекта, недостаточно в модели памяти C++ для фактического создания этого объекта. Напротив, созданная компилятором копия задействованного union поверх типов TriviallyCopyable будет тривиальной и будет работать для создания целевого объекта.

Таким образом, std::optional должен быть реализован таким образом.

person Nicol Bolas    schedule 14.09.2018
comment
Почему байтовая копия объекта TriviallyCopyable в неинициализированное хранилище не создает этот объект? Мне кажется, что так и должно быть для большинства примитивных типов, можете ли вы привести пример, почему бы и нет? - person Ron; 15.09.2018
comment
@RonMordechai: Потому что [intro.object]/1 так говорит. Вернее, в нем перечислены синтаксические конструкции, создающие объекты, и среди них нет копирования памяти. Следовательно, он не может создавать объекты. Ценности в памяти не порождают объекты сами по себе. Это обычный случай UB, который работает. - person Nicol Bolas; 15.09.2018
comment
Зачем нужно пустое поле? Будет ли это работать просто с union { T t; };? - person random; 16.12.2018
comment
@random: потому что T может не быть конструктивным по умолчанию. Или назначаемый. Когда у optional нет T, это нужно сделать так, чтобы у него не было T. И если у вас есть объединение с одним элементом, оно всегда сохраняет элемент этого типа. - person Nicol Bolas; 16.12.2018
comment
@NicolBolas Вы имеете в виду, что если союз состоит из одного члена, он будет создан автоматически? Я только что экспериментировал в Visual Studio, и конструктор не вызывается. Таким образом, похоже, что union по умолчанию не хранит никаких объектов. - person random; 16.12.2018