为什么在使用std::move时无法执行复制省略?

8

我使用下面的代码来测试复制省略:

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() 的参数是右值,会进行复制省略。但结果显示既调用了默认构造函数也调用了拷贝构造函数。为什么?

如果我将函数调用改为 g(foo()),则拷贝将被省略。 foo()std::move(a) 的返回类型有何区别?如何使编译器在左值上省略拷贝?


4
不行。g按值获取其参数,因此编译器必须确保传递的对象与调用范围内可访问的任何对象都不同。如果传递的对象是左值,则没有临时对象需要消除,也不能省略复制。 - CB Bailey
1
你预计会有多少次析构函数调用呢? ;) - curiousguy
你可能想要阅读一下std::move的实际作用。点击此处 - fredoverflow
2个回答

6

复制省略只在几种特定情况下发生,其中最常见的是复制临时对象(其他情况包括返回本地变量和抛出/捕获异常)。您的代码没有产生任何临时对象,因此不会省略复制。

复制构造函数被调用是因为 foo 没有移动构造函数(对于具有显式复制构造函数的类,不会隐式生成移动构造函数),所以 std::move(a)foo(const foo &rhs) 构造函数匹配(用于构造函数参数)。

以下情况下可以省略左值的复制(尽管没有办法“强制”编译器执行省略):

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(...) {}
}

如果你想在传递函数参数时避免复制,可以使用移动构造函数来窃取给定对象的资源:
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));
}

此外,每当允许复制省略但未发生时,如果可用,则将使用移动构造函数。

+1,值得注意的是,默认情况下没有移动构造函数,因此参数的构造必须回退到复制构造函数,即使作为右值。 - KillianDS

4

您需要将g声明为:

int g(foo && a) //accept argument as rvalue reference
{
    return 0;
}

现在它可以通过右值引用接受参数。

在您的情况下,即使表达式std::move(a)产生了右值,它也不会绑定到接受“按值传递”参数的参数。接收端必须也是右值引用

g(foo())的情况下,编译器执行复制省略,这是一种优化。 这不是语言的要求[直到C++17]。如果您想要禁用此优化,则可以:然后g(foo())g(std::move(a))将像预期的那样完全相同。

但是,如果您按照我上面的建议更改g,则调用g(foo())将不会进行复制,因为语言要求不使用&和&。 它不再是编译器优化。


6
我觉得你没有理解问题的要点。使用该签名不会有任何复制省略,因为根本就不需要复制。 - user743382
2
有人可能会认为复制省略在定义上是由编译器执行的优化(尽管我不确定这是否正确)。如果是这种情况,您的解决方案就不是复制省略。 - Björn Pollex
2
是的,你的修改很有道理。我想表达的基本上就是Björn Pollex所说的,你版本中的g(foo())并不涉及复制消除,因为调用的语义本来就没有涉及到复制。通过你的修改,我现在清楚地知道了你的意思以及它与问题的关系,即使你的措辞与我不同,所以我给你点赞。 - user743382
2
但是,如果按照我上面的建议更改g,g(foo())将省略复制。不,它不会。这不比使用const&更多地“省略”。因为语言从一开始就不允许复制,所以没有任何东西被省略。在这个上下文中,“省略”意味着删除本来会发生的事情。使用&&参数不会发生复制,因此也不能省略复制。 - Nicol Bolas
@NicolBolas:我认为你说得很对(实际上我是想表达同样的意思,只是措辞不够完美。让我编辑一下)。 - Nawaz
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接