Рассмотрим следующий пример:
#include <iostream>
struct foo {
foo(int) {}
foo(const foo&) { std::cout << "copy\n"; }
foo(foo&&) { std::cout << "move\n"; }
};
foo f() {
//return 42;
return { 42 };
}
int main() {
foo obj = f();
(void) obj;
}
При компиляции с gcc 4.8.1 с -fno-elide-constructors
для предотвращения RVO вывод
move
Если в f
используется оператор return без фигурных скобок, то вывод будет
move
move
При отсутствии RVO происходит следующее. f
должен создать временный объект типа foo
, назовем его ret
, который будет возвращен.
Если используется return { 42 };
, то ret
прямо инициализируется значением 42
. Таким образом, до сих пор не вызывался конструктор копирования/перемещения.
Если используется return 42;
, то другой временный, назовем его tmp
, напрямую инициализируется из 42
, а tmp
перемещается для создания ret
. Следовательно, до сих пор был вызван один конструктор перемещения. (Обратите внимание, что tmp
является значением r, а foo
имеет конструктор перемещения. Если бы не было конструктора перемещения, то был бы вызван конструктор копирования.)
Теперь ret
является значением r и используется для инициализации obj
. Следовательно, конструктор перемещения вызывается для перемещения от ret
к obj
. (Опять же, в некоторых случаях вместо этого может быть вызван конструктор копирования.) Следовательно, происходит одно (для return { 42 };
) или два (для return 42;
) перемещения.
Как я уже сказал в своем комментарии к вопросу ОП, этот пост очень актуален: помощник построения make_XYZ, позволяющий RVO и вывод типов, даже если XZY имеет ограничение на некопирование. Особенно отличный ответ от Р. Мартиньо Фернандес.
person
Cassio Neri
schedule
06.02.2014