Как std::Optional задерживает инициализацию? / Как реализован std::Optional?

В последнее время я заинтересовался инициализацией. Одна из вещей, которые меня особенно интересуют, это std::Optional за его способность инициализировать экземпляр типа после того, как он был объявлен. Я попытался прочитать код внутри необязательного заголовка, но код слишком «напыщенный», чтобы я мог его понять.

Как std::Optional может задержать инициализацию объекта в стеке? Я предполагаю, что он просто резервирует sizeof(<whatever_type) количество байтов в стеке, а затем повторно интерпретирует эти байты для инициализации <whatever_bytes>. Но как это делается конкретно? Как это реализовано? Как я могу реализовать это сам?

Обновлено: чтобы уточнить, я знаю, что std::Optional в основном имеет логический член, чтобы отслеживать, инициализирован ли объект или нет, и другой член, который содержит данные.

Однако я не понимаю, как необязательно можно что-то инициализировать вручную.

Как он может уничтожить объект? Как он может восстановить новый после разрушения старого?

Это соответствующая часть необязательного заголовка в libc++: github.com/llvm-mirror/libcxx/blob/…

jtbandes 24.12.2020 19:20
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
655
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

«Очевидный» способ представить std::optional<T> — использовать указание, установлено ли значение вместе с union, содержащим T, т. е. что-то вроде этого:

template <typename T>
class optional {
    bool isSet = false;
    union { T value; };
public:
    // ...
};

По умолчанию элементы в union не инициализированы. Вместо этого вам нужно будет использовать размещение new и ручное уничтожение, чтобы управлять временем жизни сущности в union. Концептуально это похоже на использование массива байтов, но компилятор обрабатывает любые требования к выравниванию.

Вот программа с некоторыми показанными операциями:

#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include <cassert>

template <typename T>
class optional {
    bool isSet = false;
    union { T value; };
    void destroy() { if (this->isSet) { this->isSet = true; this->value.~T(); } }

public:
    optional() {}
    ~optional() { this->destroy(); }
    optional& operator=(T&& v) {
        this->destroy();
        new(&this->value) T(std::move(v));
        this->isSet = true;
        return *this;
    }   

    explicit operator bool() const { return this->isSet; }
    T&       operator*()       { assert(this->isSet); return this->value; }
    T const& operator*() const { assert(this->isSet); return this->value; }
};  

int main()
{   
    optional<std::string> o, p;
    o = "hello";
    if (o) {
        std::cout << "optional='" << *o << "'\n";
    }   
}   

Аккуратный! Я думаю, что это первый вариант использования union, который я хотел бы использовать в своем собственном коде.

Eljay 24.12.2020 19:28

Спасибо! Союз это! Однако у меня есть пара вопросов. Как я могу использовать «новое» для инициализации объектов в указанном месте в стеке? Я еще мало что знаю об управлении памятью, поэтому прошу прощения, если это глупый вопрос; единственный случай, когда я знаю ключевое слово «новое», - это динамическое размещение. Кроме того, безопасно ли реализовать необязательный таким образом? Я не часто использую союзы, потому что много слышал о том, что они небезопасны. Но я не знаю почему. Так что я посмотрю это. Тем не менее, есть ли что-нибудь особенно опасное в этой реализации необязательного класса, о которой вы знаете?

INEEDANSWERS 24.12.2020 19:39

@Eljay: очевидно, std::variant<T...> - это то же самое, только с дескриптором члена вместо логического флага. unions, которые могут содержать интересные типы C++, были необходимы, чтобы разблокировать некоторые интересные функции. Есть несколько других случаев, когда отложенное построение стержней полезно.

Dietmar Kühl 24.12.2020 19:39

@INEEDANSWERS: C++ union — это объекты, о которых вам нужно заботиться явно. Вам нужно построить/уничтожить текущий установленный элемент, и доступ к другому, отличному от текущего набора, является неопределенным поведением (в случае optional есть только один, поэтому вам просто нужно убедиться, что элемент установлен при его использовании). Есть еще много вещей, о которых вам нужно знать, и в этом смысле они менее безопасны, чем другие объекты, которые предназначены для простоты использования. Однако это инструменты для создания безопасных сущностей (например, std::optional). Кстати, я расширил свой ответ, чтобы он содержал минимальную реализацию.

Dietmar Kühl 24.12.2020 19:46

Спасибо за подробное обновление вашего ответа. Считаю свой вопрос ответом. Я не знал, что союзы могут задерживать инициализацию, и если бы я это сделал, я бы использовал его раньше. Еще один вопрос: когда вы говорите «Есть больше (...) простоты использования», все опасности, о которых вы говорите, связаны с использованием союзов, верно? Итак, как только я прочитаю о профсоюзах и узнаю, на что обращать внимание, мне пора идти, верно? Или вы говорите об ошибках, связанных с другими опасностями этой реализации?

INEEDANSWERS 24.12.2020 20:01

@INEEDANSWERS: Ну, помимо того, что делает union, вам нужно знать, чтобы написать правильный шаблон класса optional. На самом деле, я только что заметил глупую ошибку, которую я сделал, и я исправлю: после destroy() изменения value код не очищается isSet, но создание нового объекта может привести к тому, что value останется неинициализированным, в то время как isSet будет утверждать, что есть ошибка. Это не имеет ничего общего с unions, но вам нужно быть бдительными, когда реализуете классы, явно вмешивающиеся в время жизни объекта (контейнеры обычно находятся в этом пространстве).

Dietmar Kühl 24.12.2020 20:10

@DietmarKühl Ах да. Ну, конечно, на такие ошибки тоже важно обращать внимание, это правда. Спасибо за помощь. Теперь, когда я знаю, что эта реализация на самом деле является базовой опциональной, я начну изучать ключевое слово 'new', семантику перемещения (заметил там std::move) и объединения. Спасибо!

INEEDANSWERS 24.12.2020 20:21

Другие вопросы по теме