Вернуть необязательно из метода класса

У меня есть необязательный член в классе, который я хочу вернуть по значению через метод. Образец кода:

#include <stdio.h>
#include <optional>
#include <iostream>
using namespace std;

class bar {
public:
    int a;

    bar(const bar &obj) {
        a = obj.a;
    }
};

class foo {
public:
    void init(){
        abc->a = 100;
    }

    optional<bar> get() {
        return abc;
    }
    
    optional<bar> abc;
};

int main()
{
    foo temp;
    temp.init();
    auto copied = temp.get();
    cout << "Expected value is 100, got: " << copied->a;
    return 0;
}

Код выводит какое-то мусорное значение.

Как я могу этого добиться?


Насколько я понимаю, необязательный хранит полностью выделенную память для базового типа (а не только ссылку), и при возврате необязательной переменной должен сработать конструктор копирования базового типа, который должен скопировать память как есть в новый возвращается необязательное значение.


person Nishant    schedule 10.09.2020    source источник
comment
Конструктор по умолчанию std::optional создает необязательный пустой (не содержит значения), поэтому ваш конструктор foo abc->a = 100; вызывает неопределенное поведение (поскольку у вас нет объекта для доступа)   -  person UnholySheep    schedule 10.09.2020
comment
Важно то, существует ли объект внутри памяти или нет; что места для одного недостаточно.   -  person molbdnilo    schedule 10.09.2020
comment
@UnholySheep Как добавить присвоить значение необязательному? member_.value() = newValue?   -  person Nishant    schedule 10.09.2020
comment
@UnholySheep @molbdnilo Я обновил пример кода, чтобы инициализировать базовый тип с помощью отдельного вызова init и вызвать API init в основной функции. Теперь базовый тип должен быть правильно инициализирован для копирования, верно?   -  person Nishant    schedule 10.09.2020
comment
Нет, value() выдает исключение, если необязательный параметр не содержит значения. Вы либо создаете необязательный параметр со значением (используя соответствующую перегрузку конструктора), либо используете перегруженный operator=, либо функцию-член emplace (если вы хотите создать объект на месте)   -  person UnholySheep    schedule 10.09.2020
comment
Класс bar не может создавать экземпляры как есть, поскольку единственный конструктор, который он предоставляет, является конструктором копирования. Вы должны добавить хотя бы конструктор по умолчанию или конструктор преобразования из int.   -  person Daniel Langr    schedule 10.09.2020


Ответы (2)


Вам нужно использовать конструктор optional, чтобы убедиться, что он содержит объект: (при условии, что избыточный конструктор копирования bar, который блокирует его создание, удален)

foo()
    : abc{bar{100}}
{
}

или после создания optional:

void init(){
    abc = bar{100};
}

В противном случае optional остается в пустом состоянии, и вызов -> для пустого optional приводит к неопределенное поведение. Конструктор копирования optional не копирует конструкцию содержащегося объекта, когда источник пуст.

person L. F.    schedule 10.09.2020
comment
Я внес некоторые изменения в свой вопрос. По сути, я не могу инициализировать необязательный элемент в конструкторе, поэтому я добавил для этого метод инициализации. Как это изменит этот ответ? - person Nishant; 10.09.2020
comment
@Nishant вместо этого используйте присваивание. emplace тоже работает. Смотрите обновление. - person L. F.; 10.09.2020
comment
Обратите внимание, что bar должен иметь подходящий конструктор преобразования, чтобы это работало. - person Daniel Langr; 10.09.2020
comment
@ Л.Ф. Мне нужен конструктор по умолчанию для панели, иначе не работает. - person Nishant; 10.09.2020
comment
@Nishant Ой, упустил из виду. Насколько я могу судить, сейчас нет (законного) способа создать объект типа bar, поэтому optional вам в этом не поможет. - person L. F.; 10.09.2020
comment
@DanielLangr действительно, я пренебрег этим. bar в любом случае нельзя использовать как есть. - person L. F.; 10.09.2020

std::optional подобен интеллектуальному указателю, по умолчанию он имеет нулевое значение, и поэтому доступ к его членам будет неопределенным поведением. Вам нужно инициализировать его перед использованием:

void init(){
  abc = bar();
  abc->a = 100;
}

Обратите внимание, что в его нынешнем виде bar нельзя создать иначе, как с помощью конструктора копирования, поэтому вам нужно добавить конструктор по умолчанию или конструктор, который принимает аргумент int, или удалить ненужный конструктор копирования.

person Alan Birtles    schedule 10.09.2020
comment
Да, я внес эти изменения в вопрос. Тем не менее я не вижу копируемого значения. Вот пример запуска: onlinegdb.com/Hk5F9IDEv - person Nishant; 10.09.2020
comment
bar для этого должен быть конструктор по умолчанию. - person Daniel Langr; 10.09.2020
comment
@Nishant работает с моим изменением (и добавлением конструктора для bar): onlinegdb.com/ryI7iLPEv - person Alan Birtles; 10.09.2020