繁体   English   中英

为什么在创建右值并将其传递给函数时会有副本?

[英]Why is there a copy when creating an rvalue and passing it to a function?

为了更好地理解复制省略,我编写了一个测试应用程序,它在复制和移动构造函数/赋值运算符中执行了一个简单的操作,并计算了它被复制或移动的次数。 然而,我注意到当我创建一个右值并直接传递它而不是创建一个左值然后将它传入时有一个副本。

我想了解这是编译器定义的还是语言规范的一部分? 此外,我试图理解为什么它不会被省略,因为它似乎应该只是一个结构而不是副本?

我测试的神马

struct Bar {
    Bar() {}

    Bar(const Bar& a)
    : cp_count{a.cp_count + 1}, mv_count{a.mv_count} {}
    Bar& operator=(const Bar& a) {
        cp_count = a.cp_count + 1;
        mv_count = a.mv_count;
        return *this;
    }

    Bar(Bar&& a)
     : cp_count{a.cp_count}, mv_count{a.mv_count + 1} {}
    Bar& operator=(Bar&& a) {
        cp_count = a.cp_count;
        mv_count = a.mv_count + 1;
        return *this;
    }

    int cp_count = 0;
    int mv_count = 0;
};

struct Foo {
    Bar bar;
    std::function<void(Bar)> setter;
    std::function<void(Bar)> setter2;
    
    Foo() {
        setter = [this](Bar a) {
            bar = a;
        };

        setter2 = [this](Bar a) {
            bar = std::move(a);
        };
    }
};

int main() {
    std::cout << std::endl << "base line" << std::endl;
    {
        Foo foo;
        std::cout << "mv_count = " << foo.bar.mv_count << " cp_count = " << foo.bar.cp_count << std::endl;
    }

    std::cout << std::endl << "in-place then copy" << std::endl;
    {
        Foo foo;
        foo.setter(Bar{});

        std::cout << "mv_count = " << foo.bar.mv_count << " cp_count = " << foo.bar.cp_count << std::endl;
    }

    std::cout << std::endl << "copy then copy" << std::endl;
    {
        Foo foo;
        Bar bar{};
        foo.setter(bar);

        std::cout << "mv_count = " << foo.bar.mv_count << " cp_count = " << foo.bar.cp_count << std::endl;
    }

    std::cout << std::endl << "move then copy" << std::endl;
    {
        Foo foo;
        Bar bar{};
        foo.setter(std::move(bar));

        std::cout << "mv_count = " << foo.bar.mv_count << " cp_count = " << foo.bar.cp_count << std::endl;
    }

    std::cout << std::endl << "in-place then move" << std::endl;
    {
        Foo foo;
        foo.setter2(Bar{});

        std::cout << "mv_count = " << foo.bar.mv_count << " cp_count = " << foo.bar.cp_count << std::endl;
    }

    std::cout << std::endl << "copy then move" << std::endl;
    {
        Foo foo;
        Bar bar{};
        foo.setter2(bar);

        std::cout << "mv_count = " << foo.bar.mv_count << " cp_count = " << foo.bar.cp_count << std::endl;
    }

    std::cout << std::endl << "move then move" << std::endl;
    {
        Foo foo;
        Bar bar{};
        foo.setter2(std::move(bar));

        std::cout << "mv_count = " << foo.bar.mv_count << " cp_count = " << foo.bar.cp_count << std::endl;
    }
}

我从测试中收到的输出

base line
mv_count = 0 cp_count = 0

in-place then copy
mv_count = 1 cp_count = 1

copy then copy
mv_count = 1 cp_count = 2

move then copy
mv_count = 2 cp_count = 1

in-place then move
mv_count = 2 cp_count = 0

copy then move
mv_count = 2 cp_count = 1

move then move
mv_count = 3 cp_count = 0

在输出中看到的额外移动是由std::function<void(Bar)> Foo的定义更改为

struct Foo 
{
    Bar bar;
    void setter(Bar a) { bar = a; }
    void setter2(Bar a) { bar = std::move(a); }
};

并且输出变成

base line
mv_count = 0 cp_count = 0

in-place then copy
mv_count = 0 cp_count = 1

copy then copy
mv_count = 0 cp_count = 2

move then copy
mv_count = 1 cp_count = 1

in-place then move
mv_count = 1 cp_count = 0

copy then move
mv_count = 1 cp_count = 1

move then move
mv_count = 2 cp_count = 0

这是你应该期待的。 例如,对于in-place then copysetter的参数a从参数 prvalue Bar{}初始化(在 C++17 中保证复制省略),然后被复制分配给bar (对Bar的复制的一次调用-赋值运算符); 总体来说,没有动作,一份副本。


setterstd::function<void(Bar)>setter(x)调用std::functionoperator()(Bar arg) ,它包装了你的 lambda 闭包对象的operator()(Bar a) 它基本上通行证std::forward<Bar>(arg)到你的operator()中添加一个移动建设astd::forward<Bar>(arg)所有的情况,这说明你看到的结果。

不能省略额外的移动结构,因为

  • std::forward<Bar>(arg)是一个 xvalue,而不是一个纯右值(不保证复制省略);
  • Bar的移动构造函数有副作用;
  • 我们不属于[class.copy.elision]/1指定的任何情况(不是return ,不是throw ,不是exception-declaration )。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM