简体   繁体   English

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

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

To better understand copy elision I wrote a test app that did a simple action in copy and move constructors/assignment operators and counted the times it was copied or moved.为了更好地理解复制省略,我编写了一个测试应用程序,它在复制和移动构造函数/赋值运算符中执行了一个简单的操作,并计算了它被复制或移动的次数。 I noticed however that there was a copy when I created an rvalue and passed it directly rather than creating a lvalue then passing it in.然而,我注意到当我创建一个右值并直接传递它而不是创建一个左值然后将它传入时有一个副本。

I'm trying to understand if this is compiler defined or part of the language specification?我想了解这是编译器定义的还是语言规范的一部分? Additionally I'm trying to understand why it wouldn't be omitted as it seems like it should just be a construction rather than a copy?此外,我试图理解为什么它不会被省略,因为它似乎应该只是一个结构而不是副本?

godbolt of what I tested 我测试的神马

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;
    }
}

The output I received from my testing我从测试中收到的输出

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

The additional moves seen in the output are caused by std::function<void(Bar)> .在输出中看到的额外移动是由std::function<void(Bar)> Change the definition of Foo toFoo的定义更改为

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

and the output becomes并且输出变成

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

which is what you should expect.这是你应该期待的。 For example, for in-place then copy , the parameter a of setter is initialized from the argument prvalue Bar{} (guaranteed copy elision in C++17), then is copy-assigned to bar (one invocation of Bar 's copy-assignment operator);例如,对于in-place then copysetter的参数a从参数 prvalue Bar{}初始化(在 C++17 中保证复制省略),然后被复制分配给bar (对Bar的复制的一次调用-赋值运算符); overall, no moves, one copy.总体来说,没有动作,一份副本。


When setter is std::function<void(Bar)> , setter(x) calls std::function 's operator()(Bar arg) , which wraps your lambda closure object's operator()(Bar a) .setterstd::function<void(Bar)>setter(x)调用std::functionoperator()(Bar arg) ,它包装了你的 lambda 闭包对象的operator()(Bar a) It basically passes std::forward<Bar>(arg) to your operator() , adding one move construction (of a from std::forward<Bar>(arg) ) to all cases, which explains the results you were seeing.它基本上通行证std::forward<Bar>(arg)到你的operator()中添加一个移动建设astd::forward<Bar>(arg)所有的情况,这说明你看到的结果。

The additional move construction cannot be elided, since不能省略额外的移动结构,因为

  • std::forward<Bar>(arg) is an xvalue, not a prvalue (no guaranteed copy elision); std::forward<Bar>(arg)是一个 xvalue,而不是一个纯右值(不保证复制省略);
  • Bar 's move constructor has side effects; Bar的移动构造函数有副作用;
  • we're not in any of the cases specified by [class.copy.elision]/1 (not a return , not a throw , not an exception-declaration ).我们不属于[class.copy.elision]/1指定的任何情况(不是return ,不是throw ,不是exception-declaration )。

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

相关问题 当按值传递右值到函数时,为什么不调用复制构造函数 - Why isn't the copy-constructor called when passing rvalue by value to function 创建右值并将其通过转发传递给函数 - Creating an rvalue and passing it to a function by forwarding 如何在通过rvalue引用函数参数时从原始类型变量进行复制 - How to copy from primitive type variables when passing through rvalue reference function arguments 将右值传递给函数 - Passing an rvalue to a function 传递函数右值引用,当它按值作为参数时 - Passing function rvalue reference, when it takes as parameter by value 禁止将右值引用传递给函数 - disallow passing of rvalue reference to a function 将rvalue传递给可能不使用它的函数 - Passing rvalue to function that may not use it 将rvalue传递给非ref参数,为什么编译器不能复制? - passing rvalue to non-ref parameter, why can't the compiler elide the copy? C ++:传递std :: unique_ptr(move-only type)rvalue作为参数时复制elision - C++: Copy elision when passing std::unique_ptr (move-only type) rvalue as parameter 为什么 std::move 复制右值或 const 左值函数参数的内容? - Why does std::move copy contents for a rvalue or const lvalue function argument?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM