首页 > 解决方案 > 值传递和 std::move 优于传递引用的优点

问题描述

我目前正在学习 C++,并尽量避免养成坏习惯。据我了解,clang-tidy 包含许多“最佳实践”,我尽量坚持使用它们(尽管我不一定明白为什么它们被认为是好的),但我不确定我是否了解这里推荐的内容。

我使用了教程中的这个类:

class Creature
{
private:
    std::string m_name;

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

这导致了 clang-tidy 的建议,即我应该通过 value 而不是 reference 和 use 传递std::move。如果我这样做了,我会收到建议进行name引用(以确保它不会每次都被复制)以及std::move不会产生任何影响的警告,因为name是一个const所以我应该删除它。

我没有收到警告的唯一方法是const完全删除:

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

这似乎是合乎逻辑的,因为唯一的好处const是防止弄乱原始字符串(这不会发生,因为我按值传递)。但我在CPlusPlus.com上读到:

尽管请注意 - 在标准库中 - 移动意味着被移动的对象处于有效但未指定的状态。这意味着,在这样的操作之后,被移动对象的值应该只被销毁或分配一个新值;否则访问它会产生一个未指定的值。

现在想象一下这段代码:

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

因为nameString按值传递,std::move只会name在构造函数内部无效,不会触及原始字符串。但是这样做有什么好处呢?无论如何,内容似乎只被复制一次 - 如果我在调用时通过引用传递m_name{name},如果我在传递它时通过值传递(然后它被移动)。我知道这比按值传递而不是使用要好std::move(因为它被复制了两次)。

所以两个问题:

  1. 我是否正确理解了这里发生的事情?
  2. std::move使用通过引用传递并只是调用有什么好处m_name{name}吗?

标签: c++

解决方案


/* (0) */ 
Creature(const std::string &name) : m_name{name} { }
  • 传递的左值绑定到name,然后复制m_name

  • 传递的右值绑定到name,然后复制m_name


/* (1) */ 
Creature(std::string name) : m_name{std::move(name)} { }
  • 传递的左值复制name中,然后被移动m_name中。

  • 传递的右值移入name然后移入m_name


/* (2) */ 
Creature(const std::string &name) : m_name{name} { }
Creature(std::string &&rname) : m_name{std::move(rname)} { }
  • 传递的左值绑定到name,然后复制m_name

  • 传递的右值绑定到rname,然后移动m_name


由于移动操作通常比副本快,如果您通过大量临时对象, (1)优于(0) 。(2)在复制/移动方面是最佳的,但需要代码重复。

完美转发可以避免代码重复:

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

您可能希望限制T以限制可以实例化此构造函数的类型域(如上所示)。C++20 旨在通过概念简化这一点。


在 C++17 中,prvalues保证复制省略的影响,当适用时,这将减少向函数传递参数时的复制/移动次数。


推荐阅读