Подтвердить что ты не робот

Возвращаемый элемент unique_ptr из метода класса

Я пытаюсь вернуть член std::unique_ptr class (пытающийся переместить право собственности) на вызывающего. Ниже приведен пример фрагмента кода:

class A {
public:
  A() : p {new int{10}} {}

  static std::unique_ptr<int> Foo(A &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) сможет в этом случае сделать оптимизацию возвращаемого значения (copy elision), не требуя явного намерения через std::move(). Но это не так. Почему бы и нет?

Следующий код, кажется, работает нормально, что кажется эквивалентным:

std::unique_ptr<int> foo() {
  std::unique_ptr<int> p {new int{10}};
  return p;
}
4b9b3361

Ответ 1

Правило в [class.copy]:

[...], когда выражение в выражении return является (возможно в скобках) id-выражение, которое называет объект с автоматическим временем хранения, объявленным в теле или параметром-объявлением-предложением самой внутренней функции включения или лямбда-выражения, разрешением перегрузки до выберите конструктор для копии сначала, как если бы объект был обозначен rvalue.

В этом примере:

std::unique_ptr<int> foo() {
  std::unique_ptr<int> p {new int{10}};
  return p;
}

p - это имя объекта с автоматическим временем хранения, объявленным в теле функции. Поэтому вместо того, чтобы копировать его в возвращаемое значение, мы сначала попытаемся его переместить. Это прекрасно работает.

Но в этом примере:

static std::unique_ptr<int> Foo(A &a) {
    return a.p;
}

который не применяется. a.p не является именем объекта вообще, поэтому мы не пытаемся перегрузить разрешение, как если бы оно было rvalue, вместо этого мы просто делаем обычную вещь: попробуйте скопировать его. Это не удается, поэтому вы должны явно указать move().


Это формулировка правила, но это может не ответить на ваш вопрос. Почему это правило? В принципе - мы пытаемся быть в безопасности. Если мы назовем локальную переменную, всегда безопасно перемещаться из нее в оператор return. Он никогда больше не будет доступен. Легкая оптимизация, отсутствие недостатков. Но в вашем исходном примере a не принадлежит этой функции, и не a.p. Из него невозможно безопасно перемещаться, поэтому язык не будет пытаться делать это автоматически.

Ответ 2

Копирование elision не может применяться (среди других причин), так как a.p является std::unique_ptr, который не подлежит сомнению. И поскольку a.p имеет время жизни за пределами тела A::Foo(A&), было бы очень удивительно (как, например, удивить человека, написавшего код), если компилятор автоматически попытался перейти от a.p, что, вероятно, повредило бы инвариантов класса a. Он будет работать, если вы return std::move(a.p);, но это явно крадет a.p.