Что мне нужно, чтобы вернуть объект с элементом unique_ptr?

Допустим, у меня есть этот объект:

struct foo {
    std::unique_ptr<int> mem;
    virtual ~foo() = default;
};

Я больше не могу возвращать объект foo, созданный в функции:

foo make_foo() {
    foo result;

    result.mem = std::make_unique<int>({});
    return result;
}

Как можно заметить, мне нужно, чтобы деструктор был виртуальным, потому что это будет базовый класс. Но даже если я использую деструктор по умолчанию, этого недостаточно, я все равно не могу вернуть объект, созданный в функции.

Я получаю сообщение об ошибке:

error C2280: foo::foo(const foo &): attempting to reference a deleted function

Есть ли способ обойти эту проблему?


person Jonathan Mee    schedule 27.09.2018    source источник
comment
Попробуйте добавить foo() = default;, foo(foo &&) = default; и foo &operator=(foo &&) = default;. Конструктор перемещения не будет генерироваться неявно, если вы определили оператор присваивания перемещения, копирующий конструктор/оператор присваивания или деструктор.   -  person StaceyGirl    schedule 27.09.2018
comment
@ user463035818 Самое последнее сообщение об ошибке: /usr/include/c++/6/bits/unique_ptr.h:359:7: примечание: здесь объявлено unique_ptr(const unique_ptr&) = delete;   -  person NathanOliver    schedule 27.09.2018
comment
@NathanOliver о, да, я слишком привык сначала исправлять вещи, поэтому я упустил это из виду.   -  person 463035818_is_not_a_number    schedule 27.09.2018


Ответы (2)


Согласно [class.copy.ctor]/8.

Если определение класса X явно не объявляет конструктор перемещения, неявный конструктор будет неявно объявлен как заданный по умолчанию тогда и только тогда, когда [...]

  • X не имеет объявленного пользователем деструктора.

С

virtual ~foo() = default;

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

Чтобы вернуть конструктор перемещения и сохранить конструктор по умолчанию, вам нужно добавить

foo() = default;
foo(foo&&) = default;
foo &operator=(foo &&) = default; // add this if you want to move assign as well

to foo


Причина, по которой вы должны добавить foo() = default; при добавлении foo(foo&&) = default;, заключается в том, что foo(foo&&) = default; является конструктором, объявленным используемым, и если у вас есть какие-либо конструкторы, объявленные пользователем, конструктор по умолчанию больше не предоставляется.


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

struct make_virtual
{
    virtual ~make_virtual() = default;
};

struct foo : make_virtual {
    std::unique_ptr<int> mem;
};
person NathanOliver    schedule 27.09.2018
comment
Я все время забываю, что деструктор по умолчанию не то же самое, что деструктор по умолчанию. Нет другого способа сделать деструктор по умолчанию виртуальным? - person Jonathan Mee; 27.09.2018
comment
@JonathanMee Нет. C ++ действительно заставляет вас платить за это в первый раз (базовый класс). В производных классах ~derived() будет неявно виртуальным. - person NathanOliver; 27.09.2018
comment
@JonathanMee На самом деле я только что добавил для вас хак. - person NathanOliver; 27.09.2018
comment
Еще одно замечание: в моем компиляторе С++ 14 установка конструктора перемещения по умолчанию устраняет ошибку только в том случае, если я изменяю оператор return для make_foo на: return move(result) Я думал, что это должно было работать автоматически? Должен ли я делать это move? - person Jonathan Mee; 27.09.2018
comment
@JonathanMee Какой компилятор и версию вы используете? По этому result следует рассматривать как rvalue, так как это локальный автоматический объект. - person NathanOliver; 27.09.2018
comment
Проклятия нет, это просто потому, что моя локально сконструированная переменная была foo&&, как только я исправил, что она перемещается правильно. - person Jonathan Mee; 27.09.2018
comment
@JonathanMee Тогда это ответит вам на все? - person NathanOliver; 27.09.2018
comment
Да у тебя все получилось. Большое спасибо. Я соглашусь, после того, как немного остынет... - person Jonathan Mee; 27.09.2018

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

struct foo {
    unique_ptr<int> mem;
    foo() = default;
    foo(const foo& copy);
    virtual ~foo() = default;
};

foo::foo(const foo& copy) : mem(new int(*copy.mem)) {}

foo make_foo() {
    foo result;
    result.mem = make_unique<int>();
    return result;
}
person Ron    schedule 27.09.2018