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
поскольку возвращаемое значение является временным объектом, который будет уничтожен при выходе из функции, что гарантирует уникальность возвращаемого указателя. Мне любопытно, как это реализовано, это специальный случай в компиляторе или есть какое-то другое предложение в спецификации языка, которое это эксплуатирует?
источник
unique_ptr
. Весь вопрос в том, что 1 и 2 - это два разных способа достижения одного и того же.main
выхода из функции, но не послеfoo
выхода.Ответы:
Да, см. 12.8 §34 и §35:
Просто хотел добавить еще одно замечание, что возвращение по значению должно быть здесь выбором по умолчанию, потому что именованное значение в операторе возврата в худшем случае, т.е. без пропусков в C ++ 11, C ++ 14 и C ++ 17, обрабатывается как ценность. Так, например, следующая функция компилируется с
-fno-elide-constructors
флагомПри установленном флаге при компиляции в этой функции происходит два хода (1 и 2), а затем один ход (3).
источник
foo()
оно действительно также должно быть уничтожено (если оно не было назначено чему-либо), точно так же, как возвращаемое значение в функции, и, следовательно, имеет смысл, что C ++ использует конструктор перемещения при выполненииunique_ptr<int> p = foo();
?std::unique_ptr
разрешение копии (даже если оно не может применяться с ), существует специальное правило, которое сначала обрабатывает объекты как значения. Я думаю, что это полностью соответствует тому, что ответил Никола.Это никоим образом не относится к
std::unique_ptr
, но относится к любому классу, который является подвижным. Это гарантируется правилами языка, так как вы возвращаетесь по значению. Компилятор пытается исключить копии, вызывает конструктор перемещения, если он не может удалить копии, вызывает конструктор копирования, если он не может перемещаться, и не может скомпилировать, если он не может копировать.Если у вас есть функция, принимающая
std::unique_ptr
в качестве аргумента, вы не сможете передать ей p. Вам бы пришлось явно вызывать конструктор перемещения, но в этом случае вы не должны использовать переменную p после вызоваbar()
.источник
p
это не временно, результат тогоfoo()
, что возвращается, есть; таким образом, это значение и может быть перемещено, что делает назначениеmain
возможным. Я бы сказал, что вы были неправы, за исключением того, что Никола, кажется, применяет это правило кp
себе, которое по ошибке.1
и линией2
? На мой взгляд , это то же самое , так как при построенииp
вmain
, он только заботится о типе возвращаемого типаfoo
, верно?unique_ptr не имеет традиционного конструктора копирования. Вместо этого у него есть «конструктор перемещения», который использует ссылки rvalue:
Ссылка rvalue (двойной амперсанд) будет связываться только с rvalue. Вот почему вы получаете ошибку, когда пытаетесь передать lvalue unique_ptr в функцию. С другой стороны, значение, возвращаемое функцией, рассматривается как значение r, поэтому конструктор перемещения вызывается автоматически.
Кстати, это будет работать правильно:
Временный unique_ptr здесь является значением.
источник
p
«явно» lvalue можно рассматривать как rvalue в выражении returnreturn p;
в определенииfoo
. Я не думаю, что есть какая-то проблема с тем фактом, что возвращаемое значение самой функции может быть «перемещено».Я думаю, что это прекрасно объясняется в статье 25 « Эффективного современного С ++» Скотта Мейерса . Вот выдержка:
Здесь RVO относится к оптимизации возвращаемого значения , и если условия для RVO выполняются, значит возвращать локальный объект, объявленный внутри функции, которую вы ожидаете выполнить в RVO , что также хорошо объясняется в пункте 25 его книги, ссылаясь на стандарт (здесь локальный объект включает временные объекты, созданные оператором return). Самым большим отрывом от выдержки является либо исключение копии, либо
std::move
неявное применение к возвращаемым локальным объектам . Скотт упоминает в пункте 25, чтоstd::move
неявно применяется, когда компилятор решает не исключать копию, а программист не должен явно делать это.В вашем случае код явно является кандидатом в RVO, так как он возвращает локальный объект,
p
а его типp
совпадает с типом возвращаемого значения, что приводит к удалению копии. И если бы компилятор решил не исключать копию по какой-либо причине,std::move
он бы включился в строку1
.источник
Одна вещь, которую я не видел в других ответахЧтобы уточнить другие ответы , существует разница между возвратом std :: unique_ptr, который был создан внутри функции, и тем, который был передан этой функции.Пример может быть таким:
источник