简体   繁体   English

值传递和 std::move 优于传递引用的优点

[英]Advantages of pass-by-value and std::move over pass-by-reference

I'm learning C++ at the moment and try avoid picking up bad habits.我目前正在学习 C++,并尽量避免养成坏习惯。 From what I understand, clang-tidy contains many "best practices" and I try to stick to them as best as possible (even though I don't necessarily understand why they are considered good yet), but I'm not sure if I understand what's recommended here.据我了解,clang-tidy 包含许多“最佳实践”,我尽量坚持使用它们(尽管我不一定明白为什么它们被认为是好的),但我不确定我是否了解这里推荐的内容。

I used this class from the tutorial:我使用了教程中的这个类:

class Creature
{
private:
    std::string m_name;

public:
    Creature(const std::string &name)
            :  m_name{name}
    {
    }
};

This leads to a suggestion from clang-tidy that I should pass by value instead of reference and use std::move .这导致 clang-tidy 建议我应该按值而不是引用传递并使用std::move If I do, I get the suggestion to make name a reference (to ensure it does not get copied every time) and the warning that std::move won't have any effect because name is a const so I should remove it.如果我这样做了,我会收到将name设为引用的建议(以确保它不会每次都被复制)以及std::move不会有任何效果的警告,因为name是一个const所以我应该删除它。

The only way I don't get a warning is by removing const altogether:我没有收到警告的唯一方法是完全删除const

Creature(std::string name)
        :  m_name{std::move(name)}
{
}

Which seems logical, as the only benefit of const was to prevent messing with the original string (which doesn't happen because I passed by value).这似乎是合乎逻辑的,因为const的唯一好处是防止弄乱原始字符串(这不会发生,因为我按值传递)。 But I read on CPlusPlus.com :但我在CPlusPlus.com上读到:

Although note that -in the standard library- moving implies that the moved-from object is left in a valid but unspecified state.尽管请注意 - 在标准库中 - 移动意味着被移动的对象处于有效但未指定的状态。 Which means that, after such an operation, the value of the moved-from object should only be destroyed or assigned a new value;这意味着,在这样的操作之后,被移动对象的值应该只被销毁或分配一个新值; accessing it otherwise yields an unspecified value.否则访问它会产生一个未指定的值。

Now imagine this code:现在想象一下这段代码:

std::string nameString("Alex");
Creature c(nameString);

Because nameString gets passed by value, std::move will only invalidate name inside the constructor and not touch the original string.因为nameString是按值传递的,所以std::move只会使构造函数内的name无效,而不会触及原始字符串。 But what are the advantages of this?但是这样做有什么好处呢? It seems like the content gets copied only once anyhow - if I pass by reference when I call m_name{name} , if I pass by value when I pass it (and then it gets moved).无论如何,内容似乎只被复制一次 - 如果我在调用m_name{name}时通过引用传递,如果我在传递它时通过值传递(然后它被移动)。 I understand that this is better than passing by value and not using std::move (because it gets copied twice).我知道这比按值传递而不使用std::move更好(因为它被复制了两次)。

So two questions:所以两个问题:

  1. Did I understand correctly what is happening here?我是否正确理解了这里发生的事情?
  2. Is there any upside of using std::move over passing by reference and just calling m_name{name} ?使用std::move通过引用传递和调用m_name{name}有什么好处吗?
/* (0) */ 
Creature(const std::string &name) : m_name{name} { }
  • A passed lvalue binds to name , then is copied into m_name .传递的左值绑定到name ,然后复制m_name

  • A passed rvalue binds to name , then is copied into m_name .传递的右值绑定到name ,然后复制m_name中。


/* (1) */ 
Creature(std::string name) : m_name{std::move(name)} { }
  • A passed lvalue is copied into name , then is moved into m_name .传递的左值复制name中,然后被移动m_name中。

  • A passed rvalue is moved into name , then is moved into m_name .传递的右值移动name中,然后被移动m_name中。


/* (2) */ 
Creature(const std::string &name) : m_name{name} { }
Creature(std::string &&rname) : m_name{std::move(rname)} { }
  • A passed lvalue binds to name , then is copied into m_name .传递的左值绑定到name ,然后复制m_name

  • A passed rvalue binds to rname , then is moved into m_name .传递的右值绑定到rname ,然后移动m_name


As move operations are usually faster than copies, (1) is better than (0) if you pass a lot of temporaries.由于移动操作通常比副本快,如果您通过大量临时对象, (1)优于(0) (2) is optimal in terms of copies/moves, but requires code repetition. (2)在复制/移动方面是最佳的,但需要代码重复。

The code repetition can be avoided with perfect forwarding :完美转发可以避免代码重复:

/* (3) */
template <typename T,
          std::enable_if_t<
              std::is_convertible_v<std::remove_cvref_t<T>, std::string>, 
          int> = 0
         >
Creature(T&& name) : m_name{std::forward<T>(name)} { }

You might optionally want to constrain T in order to restrict the domain of types that this constructor can be instantiated with (as shown above).您可能希望约束T以限制可以实例化此构造函数的类型域(如上所示)。 C++20 aims to simplify this with Concepts . C++20 旨在通过概念简化这一点。


In C++17, prvalues are affected by guaranteed copy elision , which - when applicable - will reduce the number of copies/moves when passing arguments to functions.在 C++17 中, prvalues保证复制省略的影响,当适用时,这将减少向函数传递参数时的复制/移动次数。

  1. Did I understand correctly what is happening here?我是否正确理解了这里发生的事情?

Yes.是的。

  1. Is there any upside of using std::move over passing by reference and just calling m_name{name} ?使用std::move通过引用传递和调用m_name{name}有什么好处吗?

An easy to grasp function signature without any additional overloads.一个易于掌握的函数签名,没有任何额外的重载。 The signature immediately reveals that the argument will be copied - this saves callers from wondering whether a const std::string& reference might be stored as a data member, possibly becoming a dangling reference later on.签名立即显示该参数将被复制 - 这使调用者不必怀疑const std::string&引用是否可能存储为数据成员,以后可能会成为悬空引用。 And there is no need to overload on std::string&& name and const std::string& arguments to avoid unnecessary copies when rvalues are passed to the function.并且不需要重载std::string&& nameconst std::string&参数以避免在将右值传递给函数时不必要的复制。 Passing an lvalue传递左值

std::string nameString("Alex");
Creature c(nameString);

to the function that takes its argument by value causes one copy and one move construction.通过值获取参数的函数会导致一个副本和一个移动构造。 Passing an rvalue to the same function将右值传递给同一个函数

std::string nameString("Alex");
Creature c(std::move(nameString));

causes two move constructions.导致两个移动结构。 In contrast, when the function parameter is const std::string& , there will always be a copy, even when passing an rvalue argument.相反,当函数参数为const std::string&时,即使传递右值参数,也总会有一个副本。 This is clearly an advantage as long as the argument type is cheap to move-construct (this is the case for std::string ).只要参数类型对移动构造便宜(这是std::string的情况),这显然是一个优势。

But there is a downside to consider: the reasoning doesn't work for functions that assign the function argument to another variable (instead of initializing it):但是有一个缺点需要考虑:推理不适用于将函数参数分配给另一个变量(而不是初始化它)的函数:

void setName(std::string name)
{
    m_name = std::move(name);
}

will cause a deallocation of the resource that m_name refers to before it's reassigned.将导致m_name在重新分配之前引用的资源重新分配。 I recommend reading Item 41 in Effective Modern C++ and also this question .我建议阅读 Effective Modern C++ 中的第 41 条以及这个问题

How you pass is not the only variable here, what you pass makes the big difference between the two.你如何通过不是这里唯一的变量,你通过什么使两者之间有很大的不同。

In C++, we have all kinds of value categories and this "idiom" exists for cases where you pass in an rvalue (such as "Alex-string-literal-that-constructs-temporary-std::string" or std::move(nameString) ), which results in 0 copies of std::string being made (the type does not even have to be copy-constructible for rvalue arguments), and only uses std::string 's move constructor.在 C++ 中,我们有各种各样的值类别,并且这种“惯用语”适用于传入右值的情况(例如"Alex-string-literal-that-constructs-temporary-std::string"std::move(nameString) ),这会导致生成0 个std::string副本(对于右值参数,该类型甚至不必是可复制构造的),并且只使用std::string的移动构造函数。

Somewhat related Q&A . 有点相关的问答

There are several disadvantages of pass-by-value-and-move approach over pass-by-(rv)reference:与 pass-by-(rv)reference 相比,pass-by-value-and-move 方法有几个缺点:

  • it causes 3 objects to be spawned instead of 2;它导致生成 3 个对象而不是 2 个;
  • passing an object by value may lead to extra stack overhead, because even regular string class is typically at least 3 or 4 times larger than a pointer;按值传递对象可能会导致额外的堆栈开销,因为即使是常规字符串类通常也至少比指针大 3 或 4 倍;
  • argument objects construction is going to be done on the caller side, causing code bloat;参数对象的构造将在调用方完成,导致代码膨胀;

In my case, switching to pass by value and then doing a std:move caused a heap-use-after-free error in Address Sanitizer.就我而言,切换到按值传递然后执行 std:move 会导致 Address Sanitizer 中出现 heap-use-after-free 错误。

https://travis-ci.org/github/acgetchell/CDT-plusplus/jobs/679520360#L3165 https://travis-ci.org/github/acgetchell/CDT-plusplus/jobs/679520360#L3165

So, I've turned it off, as well as the suggestion in clang-tidy.所以,我把它关掉了,还有clang-tidy中的建议。

https://github.com/acgetchell/CDT-plusplus/compare/80c96789f0a2...0d78fd63b332 https://github.com/acgetchell/CDT-plusplus/compare/80c96789f0a2..​​.0d78fd63b332

I'm learning C++ at the moment and try avoid picking up bad habits.我目前正在学习C ++,并尝试避免养成不良习惯。 From what I understand, clang-tidy contains many "best practices" and I try to stick to them as best as possible (even though I don't necessarily understand why they are considered good yet), but I'm not sure if I understand what's recommended here.据我了解,clang-tidy包含许多“最佳实践”,并且我会尽力做到最好(即使我不一定理解为什么它们仍然被认为是好的),但是我不确定我是否了解这里的建议。

I used this class from the tutorial:我从教程中使用了此类:

class Creature
{
private:
    std::string m_name;

public:
    Creature(const std::string &name)
            :  m_name{name}
    {
    }
};

This leads to a suggestion from clang-tidy that I should pass by value instead of reference and use std::move .这导致clang-tidy建议我应该按值传递而不是引用,并使用std::move If I do, I get the suggestion to make name a reference (to ensure it does not get copied every time) and the warning that std::move won't have any effect because name is a const so I should remove it.如果这样做,我得到建议将name为引用(以确保每次都不会被复制),并且警告说std::move不会起作用,因为nameconst所以我应该将其删除。

The only way I don't get a warning is by removing const altogether:我没有收到警告的唯一方法是完全删除const

Creature(std::string name)
        :  m_name{std::move(name)}
{
}

Which seems logical, as the only benefit of const was to prevent messing with the original string (which doesn't happen because I passed by value).这似乎是合乎逻辑的,因为const的唯一好处是可以防止与原始字符串混淆(因为我按值传递,所以不会发生)。 But I read on CPlusPlus.com :但是我在CPlusPlus.com阅读

Although note that -in the standard library- moving implies that the moved-from object is left in a valid but unspecified state.尽管要注意-在标准库中-移动意味着从中移出的对象处于有效但未指定的状态。 Which means that, after such an operation, the value of the moved-from object should only be destroyed or assigned a new value;这意味着,在执行此操作后,仅应销毁移出对象的值或为其分配新值; accessing it otherwise yields an unspecified value.否则访问它会产生未指定的值。

Now imagine this code:现在想象一下这段代码:

std::string nameString("Alex");
Creature c(nameString);

Because nameString gets passed by value, std::move will only invalidate name inside the constructor and not touch the original string.因为nameString是通过值传递的,所以std::move将仅使构造函数中的name无效,而不接触原始字符串。 But what are the advantages of this?但是,这样做的好处是什么? It seems like the content gets copied only once anyhow - if I pass by reference when I call m_name{name} , if I pass by value when I pass it (and then it gets moved).似乎该内容仅被复制一次-如果我在调用m_name{name}时通过引用传递,如果在传递它时按值传递(然后它被移动)。 I understand that this is better than passing by value and not using std::move (because it gets copied twice).我知道这比按值传递而不使用std::move更好(因为它被复制了两次)。

So two questions:有两个问题:

  1. Did I understand correctly what is happening here?我是否正确理解这里发生的事情?
  2. Is there any upside of using std::move over passing by reference and just calling m_name{name} ?使用std::move通过引用传递和仅调用m_name{name}什么m_name{name}吗?

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

相关问题 区分传递参考和传递价值 - Distinguishing Pass-by-Reference and Pass-by-Value C ++中按值传递和按引用传递之间的区别 - Differences between pass-by-value and pass-by-reference in c++ 我应该在哪里更喜欢按引用传递或按值传递? - Where should I prefer pass-by-reference or pass-by-value? 区分函数模板中的值传递和引用传递 - Distinguish between pass-by-value and pass-by-reference in a function template 函数参数按值传递比按引用传递更快? - Function argument pass-by-value faster than pass-by-reference? 通过查看装配将值传递与引用传递性能进行比较 - Comparing pass-by-value with pass-by-reference performance by looking at assembly 引用超载,与单值传递+ std :: move相比? - Overload on reference, versus sole pass-by-value + std::move? 按值传递和 std::move 与转发参考 - Pass-by-value and std::move vs forwarding reference 将按引用传递和按值传递混合到可变参数模板函数是否有效? - Mixing pass-by-reference and pass-by-value to variadic template function valid? C++:赋值运算符:按值传递(复制和交换)与按引用传递 - C++: assignment operator: pass-by-value (copy-and-swap) vs pass-by-reference
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM