Почему copy elision не работает с std:: move?
Я использую приведенный ниже код для тестирования копирования elision:
class foo
{
public:
foo() {cout<<"ctor"<<endl;};
foo(const foo &rhs) {cout<<"copy ctor"<<endl;}
};
int g(foo a)
{
return 0;
}
int main()
{
foo a;
g(std::move(a));
return 0;
}
Я ожидал, что будет вызван только конструктор по умолчанию, потому что аргумент g() является rvalue и копия будет удалена. Но результат показывает, что вызываются как конструктор по умолчанию, так и конструктор копирования. Почему?
И если я изменю вызов функции на g(foo()), копия будет удалена. В чем разница между возвращаемыми типами foo() и std::move(a)? Как я могу заставить компилятор игнорировать копию на именующее?
2 ответов:
Копирование elision for может происходить только в нескольких конкретных ситуациях, наиболее распространенной из которых является копирование временного (остальные-это возврат локальных объектов и выбрасывание/перехват исключений). Ваш код не создает временного кода, поэтому никакая копия не будет удалена.
Конструктор копирования вызывается потому, что
fooне имеет конструктора перемещения (конструкторы перемещения не создаются неявно для классов с явными конструкторами копирования), и поэтомуstd::move(a)соответствует конструкторуfoo(const foo &rhs)(который используется для построения аргумента функции).Копия lvalue может быть извлечена в следующих ситуациях (хотя нет способа заставить компилятор выполнить выделение):
foo fn() { foo localAutomaticVariable; return localAutomaticVariable; //Copy to construct return value may be elided } int main() { try { foo localVariable; throw localVariable; //The copy to construct the exception may be elided } catch(...) {} }Если вы хотите избежать копирования при передаче аргументов функции, вы можете использовать конструктор move, который крадет ресурсы данных ему объектов:
class bar { public: bar() {cout<<"ctor"<<endl;}; bar(const bar &rhs) {cout<<"copy ctor"<<endl;} bar(bar &&rhs) {cout<<"move ctor"<<endl;} }; void fn(bar a) { } //Prints: //"ctor" //"move ctor" int main() { bar b; f(std::move(b)); }Кроме того, всякий раз, когда копирование elision разрешено, но не происходит, конструктор перемещения будет использоваться, если это так. доступный.
Вам нужно объявить
gКак:int g(foo && a) //accept argument as rvalue reference { return 0; }Теперь он может принимать аргумент по rvalue-ссылке.
В вашем случае, даже если выражение
std::move(a)производит rvalue, оно не привязывается к параметру, который принимает аргумент по значению. Принимающий конец также должен быть rvalue-reference.В случае
Но если вы изменитеg(foo()), копирование-elision выполняется компилятором, что является оптимизацией. это не требование языка.[ до тех пор, пока С++17]. Вы можете отключить эту оптимизацию, если хотите : тогдаg(foo())иg(std::move(a))будут вести себя точно так же, как и ожидалось.g, Как я предложил выше, вызовg(foo())не сделает копию, потому что является требованием языка не делать копию с &&. Это больше не оптимизация компилятора.
Comments