#c #c 11 #smart-pointers #rvo
#c #c 11 #интеллектуальные указатели #rvo
Вопрос:
Я пытаюсь вернуть std::unique_ptr class
элемент (пытаясь передать право собственности) вызывающему. Ниже приведен пример фрагмента кода:
class A {
public:
A() : p {new int{10}} {}
static std::unique_ptr<int> Foo(A amp;a) {
return a.p; // ERROR: Copy constructor getting invoked
// return std::move(a.p); WORKS FINE
}
std::unique_ptr<int> p;
};
Я думал, что компилятор (gcc-5.2.1) сможет выполнить оптимизацию возвращаемого значения (исключение копирования) в этом случае, не требуя явного намерения через std::move()
. Но это не так. Почему бы и нет?
Следующий код, похоже, работает нормально, что кажется эквивалентным:
std::unique_ptr<int> foo() {
std::unique_ptr<int> p {new int{10}};
return p;
}
Комментарии:
1. Это отличный первый вопрос. Добро пожаловать в StackOverflow!
Ответ №1:
Правило в [class.copy] является:
[…] когда выражение в
return
операторе является (возможно, заключенным в скобки) выражением идентификатора, которое именует объект с автоматической продолжительностью хранения, объявленной в теле или предложении объявления параметра самой внутренней функции или лямбда-выражения, разрешение перегрузки для выбора конструктора для копии сначала выполняется, как если быобъект был обозначен значением rvalue .
В этом примере:
std::unique_ptr<int> foo() {
std::unique_ptr<int> p {new int{10}};
return p;
}
p
это имя объекта с автоматической продолжительностью хранения, объявленное в теле функции. Поэтому вместо того, чтобы копировать его в возвращаемое значение, мы сначала пытаемся переместить его. Это работает нормально.
Но в этом примере:
static std::unique_ptr<int> Foo(A amp;a) {
return a.p;
}
это не применяется. a.p
это вообще не имя объекта, поэтому мы не пытаемся разрешить перегрузку, как если бы это было значение rvalue , вместо этого мы просто делаем обычную вещь: пытаемся скопировать его. Это не удается, поэтому вы должны явно move()
указать это.
Это формулировка правила, но оно может не ответить на ваш вопрос. Почему это правило? В принципе — мы пытаемся быть в безопасности. Если мы присваиваем имя локальной переменной, всегда безопасно перейти от нее к оператору return . К нему больше никогда не будет доступа. Простая оптимизация, никаких возможных недостатков. Но в вашем исходном примере a
эта функция не принадлежит ни этой функции, ни a.p
ей . Переход от него по своей сути небезопасен, поэтому язык не будет пытаться сделать это автоматически.
Ответ №2:
Исключение копирования не может быть применено (среди других причин), поскольку a.p
является a std::unique_ptr
, которое невозможно скопировать. И поскольку a.p
имеет время жизни за пределами тела A::Foo(Aamp;)
, было бы очень удивительно (например, удивительно для человека, пишущего код), если бы компилятор автоматически попытался перейти из a.p
, что, вероятно, разрушило бы инварианты класса a
. Это сработало бы, если бы вы return std::move(a.p);
, но это явно крадет a.p
.
Комментарии:
1. Исключение копирования может применяться к типам только для перемещения (как во втором примере OP)
2. @Barry ХОРОШО, мое первое предложение неверно. Исключение копирования также применяется к перемещениям. Тем не менее, я считаю, что остальная часть ответа остается в силе.
3. @AndreKostur: поскольку разработчик явно пытается вернуть unique_ptr, конструктор копирования которого удален, единственным вариантом является перемещение, и удаление копирования должно работать нормально (IMO). Что меня действительно удивило, так это ошибка компиляции, а не предупреждение.