C ++ 11: двусмысленность перемещения / копирования конструкции? - PullRequest
22 голосов
/ 06 февраля 2012

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

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

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

Ответы [ 4 ]

28 голосов
/ 06 февраля 2012

Обычно это не происходит из-за RVO .

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

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

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

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

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

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

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

[Примечание: выражение является xvalue, если оно:

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

Как правило, эффект этого правила заключается в том, что именованные ссылки на rvalue рассматриваются как lvalues, а неназванные rvalue ссылки на объекты рассматриваются как значения x; Rvalue ссылки на функции обрабатываются как l-значения, именованные или нет. —Конечная записка]

<ч />

Например, если 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);
8 голосов
/ 06 февраля 2012

Для резервного копирования @Seth, вот соответствующий абзац из стандарта:

§12.8 [class.copy] p32

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

3 голосов
/ 06 февраля 2012

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

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 && -конструктору, если таковой имеется, и к конструктору const-copy в противном случае.

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

2 голосов
/ 06 февраля 2012
Foo MoveAFoo() {
  Foo f;
  return f;
}

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

В этом коде:

Foo x = MoveAFoo();

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

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

Foo x;
x = MoveAFoo();

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...