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

С++ 11: перемещение/копирование неоднозначности конструкции?

В С++ 11 мы можем определить конструкторы копирования и перемещения, но допустимы ли они в одном классе? Если да, то как вы устраните их использование? Например:

Foo MoveAFoo() {
  Foo f;
  return f;
}

Является ли вышеуказанная копия? Движение? Откуда я знаю?

4b9b3361

Ответ 1

Обычно это не будет из-за RVO.

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

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

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

Естественно, это означает, что вы можете иметь как конструктор перемещения, так и конструктор копирования в том же классе. Вы также можете иметь оператор присваивания копии и оператор присваивания переадресации.

Обновление: Возможно, неясно, когда именно вызывается оператор конструктора перемещения/присваивания по сравнению с оператором конструктора/назначения простой копии. Если я правильно понимаю, конструктор перемещения вызывается, если объект инициализируется значением xvalue (значение eXpiring). В §3.10.1 стандарта говорится:

Значение xvalue (значение "eXpiring" ) также относится к объекту, обычно близкому конец его жизни (чтобы его ресурсы могли перемещаться, для пример). Значение x является результатом определенных видов выражений с использованием ссылок rvalue (8.3.2). [Пример: результат вызова функция, возвращающим тип которой является ссылкой rvalue, является значением x. -конец пример]

И в начале § 5 стандарта говорится:

[Примечание: выражение представляет собой значение x, если оно:

  • результат вызова функции, неявно или явно, чей тип возврата является Ссылка на тип объекта,
  • ссылка на ссылку rvalue для тип объекта,
  • выражение доступа к члену класса, обозначающее нестатический элемент данных не ссылочного типа, в котором объект выражение является значением xvalue или
  • a. * выражение "указатель-член" в который первым операндом является значение x, а второй операнд указатель на элемент данных.

В общем, эффект этого правила заключается в том, что Названные ссылки rvalue рассматриваются как lvalues ​​и unnamed rvalue ссылки на объекты рассматриваются как xvalues; ссылки на функции обрабатываются как lvalues, именованные или нет. -end note]


В качестве примера, если NRVO можно сделать, он выглядит следующим образом:

void MoveAFoo(Foo* f) {
    new (f) Foo;
}

Foo myfoo; // pretend this isn't default constructed
MoveAFoo(&myfoo);

Если NRVO не может быть выполнено, но Foo является подвижным, тогда ваш пример немного выглядит следующим образом:

void MoveAFoo(Foo* fparam) {
    Foo f;

    new (fparam) Foo(std::move(f));
}

Foo f; // pretend this isn't being default constructed
MoveAFoo(&f);

И если он не может быть перемещен, но он может быть скопирован, то он вроде этого

void MoveAFoo(Foo* fparam) {
    Foo f;

    new (fparam) Foo((Foo&)f);
}

Foo f; // pretend this isn't default constructed
MoveAFoo(&f);

Ответ 2

Чтобы создать резервную копию @Seth, вот соответствующий параграф из стандарта:

§12.8 [class.copy] p32

Когда критерии для выполнения операции копирования выполняются или выполняются, за исключением того факта, что исходный объект является параметром функции, , а подлежащий копированию объект определяется значением lvalue, разрешением перегрузки для выбора конструктор для копии сначала выполняется так, как если бы объект был обозначен rvalue. Если сбой при перегрузке или если тип первого параметра выбранного конструктора не является ссылкой rvalue на тип объекта (возможно, с квалификацией cv), разрешение перегрузки выполняется снова, считая объект как lvalue. [Примечание. Это двухступенчатое разрешение перегрузки должно выполняться независимо от того, произойдет ли копирование. Он определяет вызывающий конструктор, если elision не выполняется, и выбранный конструктор должен быть доступен, даже если вызов отменяется. -end note]

Ответ 3

"Целостность" - это просто ваш старый друг, разрешение перегрузки:

Foo y;

Foo x(y);            // copy
Foo x(std::move(y)); // move

Выражение y в первом примере - это lvalue типа Foo, который привязывается к Foo const & (а также Foo &, если у вас есть такой конструктор); тип выражения std::move(y) во втором примере равен Foo &&, поэтому он будет привязан к Foo && (а также Foo const & отсутствует первый).

В вашем примере результат MoveAFoo() является временным типом Foo, поэтому он будет привязан к Foo && -constructor, если он доступен, и конструктору const-copy в противном случае.

Наконец, в функции, возвращающей Foo (по значению), оператор return x; эквивалентен return std::move(x);, если x является локальной переменной типа Foo - это специальное новое правило для облегчить использование семантики перемещения.

Ответ 4

Foo MoveAFoo() {
  Foo f;
  return f;
}

Это определение функции MoveAFoo, которая возвращает объект типа Foo. В своем теле локальный Foo f; создается и разрушается, когда он выходит из его области.

В этом коде:

Foo x = MoveAFoo();

object Foo f; создается внутри функции MoveAFoo и непосредственно назначается в x, что означает, что конструктор копирования не вызывается.

Но в этом коде:

Foo x;
x = MoveAFoo();

объект Foo f; создается внутри функции MoveAFoo, тогда копия f создается и сохраняется в x, а исходный f разрушается.