Возвращение unique_ptr из функций - PullRequest
308 голосов
/ 30 ноября 2010

unique_ptr<T> не позволяет создавать копии, вместо этого он поддерживает семантику перемещения. Тем не менее, я могу вернуть unique_ptr<T> из функции и присвоить возвращаемое значение переменной.

#include <iostream>
#include <memory>

using namespace std;

unique_ptr<int> foo()
{
  unique_ptr<int> p( new int(10) );

  return p;                   // 1
  //return move( p );         // 2
}

int main()
{
  unique_ptr<int> p = foo();

  cout << *p << endl;
  return 0;
}

Код выше компилируется и работает как задумано. Так как получается, что строка 1 не вызывает конструктор копирования и не приводит к ошибкам компилятора? Если бы мне пришлось использовать строку 2 вместо этого, это имело бы смысл (использование строки 2 также работает, но мы не обязаны это делать).

Я знаю, что C ++ 0x допускает это исключение для unique_ptr, поскольку возвращаемое значение является временным объектом, который будет уничтожен сразу после выхода из функции, что гарантирует уникальность возвращаемого указателя. Мне любопытно, как это реализовано, это особый случай в компиляторе или есть какое-то другое предложение в спецификации языка, которое это эксплуатирует?

Ответы [ 5 ]

195 голосов
/ 30 ноября 2010

существует ли какой-либо другой пункт в спецификации языка, который он использует?

Да, см. 12.8 §34 и §35:

Когда определенные критериивстречаются, реализация может опустить конструкцию копирования / перемещения объекта класса [...] Это исключение операций копирования / перемещения, называемое разрешение копирования , разрешено [...] воператор return в функции с типом возврата класса, , когда выражение является именем энергонезависимого автоматического объекта с тем же типом cv-unqualified, что и у функции, возвращаемой типом [...]

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


Просто хотел добавить еще один момент, согласно которому возвращение по значению должно быть выбором по умолчанию, поскольку именованное значение в возвращаемых государственных деятеляхВ худшем случае, т. е. без исключений в C ++ 11, C ++ 14 и C ++ 17, рассматривается как значение.Так, например, следующая функция компилируется с флагом -fno-elide-constructors

std::unique_ptr<int> get_unique() {
  auto ptr = std::unique_ptr<int>{new int{2}}; // <- 1
  return ptr; // <- 2, moved into the to be returned unique_ptr
}

...

auto int_uptr = get_unique(); // <- 3

При установленном флаге при компиляции в этой функции происходит два хода (1 и 2), а затем один ход позже (3),

92 голосов
/ 30 ноября 2010

Это никак не относится к std::unique_ptr, но относится к любому классу, который является подвижным. Это гарантируется правилами языка, так как вы возвращаетесь по значению. Компилятор пытается удалить копии, вызывает конструктор перемещения, если он не может удалить копии, вызывает конструктор копирования, если он не может перемещаться, и не компилируется, если он не может копировать.

Если бы у вас была функция, которая принимает std::unique_ptr в качестве аргумента, вы не смогли бы передать ей p. Вам бы пришлось явно вызывать конструктор перемещения, но в этом случае вы не должны использовать переменную p после вызова bar().

void bar(std::unique_ptr<int> p)
{
    // ...
}

int main()
{
    unique_ptr<int> p = foo();
    bar(p); // error, can't implicitly invoke move constructor on lvalue
    bar(std::move(p)); // OK but don't use p afterwards
    return 0;
}
36 голосов
/ 01 декабря 2010

unique_ptr не имеет традиционного конструктора копирования. Вместо этого он имеет «конструктор перемещения», который использует ссылки rvalue:

unique_ptr::unique_ptr(unique_ptr && src);

Ссылка на rvalue (двойной амперсанд) будет привязана только к значению r. Вот почему вы получаете ошибку, когда пытаетесь передать lvalue unique_ptr в функцию. С другой стороны, значение, возвращаемое функцией, рассматривается как значение r, поэтому конструктор перемещения вызывается автоматически.

Кстати, это будет работать правильно:

bar(unique_ptr<int>(new int(44));

Временный unique_ptr здесь является значением.

9 голосов
/ 04 октября 2017

Я думаю, что это прекрасно объяснено в пункте 25 Скотта Мейерса Effective Modern C ++ .Вот выдержка:

В части стандарта, благословляющего RVO, говорится, что если условия для RVO выполнены, но компиляторы решают не выполнять копирование, возвращаемый объект должен бытьрассматривается как значение.По сути, Стандарт требует, чтобы при разрешении RVO происходило либо исключение копирования, либо std::move неявно применяется к возвращаемым локальным объектам.

Здесь RVO относится оптимизация возвращаемого значения и , если выполняются условия для RVO означает возврат локального объекта, объявленного внутри функции, который вы ожидаете выполнить RVO ,что также хорошо объясняется в пункте 25 его книги со ссылкой на стандарт (здесь локальный объект включает временные объекты, созданные оператором return).Самый большой отрывок отрывка - , либо имеет место удаление копии, либо std::move неявно применяется к возвращаемым локальным объектам .Скотт упоминает в пункте 25, что std::move неявно применяется, когда компилятор решает не удалять копию, а программист не должен явно делать это.

В вашем случае код явно является кандидатом на RVO , поскольку он возвращает локальный объект p, а тип p совпадает с типом возврата, что приводит к удалению копии.И если по какой-либо причине компилятор решит не удалять копию, std::move вставит строку 1.

2 голосов
/ 03 июля 2017

Одна вещь, которую я не видел в других ответах, это Чтобы уточнить другие ответы , что существует разница между возвратом std :: unique_ptr, который был создан внутри функции, и тот, который был дан этой функции.

Пример может быть таким:

class Test
{int i;};
std::unique_ptr<Test> foo1()
{
    std::unique_ptr<Test> res(new Test);
    return res;
}
std::unique_ptr<Test> foo2(std::unique_ptr<Test>&& t)
{
    // return t;  // this will produce an error!
    return std::move(t);
}

//...
auto test1=foo1();
auto test2=foo2(std::unique_ptr<Test>(new Test));
...