Читая реализацию std::optional GCC, я заметил кое-что интересное. Я знаю, что 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_;
}

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

13
Ron 14 Сен 2018 в 22:34

2 ответа

Лучший ответ

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

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

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

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

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

13
Barry 14 Сен 2018 в 19:52

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

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

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

12
Nicol Bolas 14 Сен 2018 в 19:41