Почему 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)? Как я могу заставить компилятор игнорировать копию на именующее?

621   2  

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

    Ничего не найдено.