传递值和std::move over pass by reference的优点 我现在正在学习C++,尽量避免养成坏习惯。 据我所知,clang tidy包含许多“最佳实践”,我尽可能地坚持这些实践(尽管我还不一定理解为什么它们被认为是好的),但我不确定我是否理解这里推荐的内容
我使用了教程中的这个类:传递值和std::move over pass by reference的优点 我现在正在学习C++,尽量避免养成坏习惯。 据我所知,clang tidy包含许多“最佳实践”,我尽可能地坚持这些实践(尽管我还不一定理解为什么它们被认为是好的),但我不确定我是否理解这里推荐的内容,c++,C++,我使用了教程中的这个类: class Creature { private: std::string m_name; public: Creature(const std::string &name) : m_name{name} { } }; 这导致了clang tidy的建议,即我应该通过值而不是引用传递,并使用std::move。 如果我这样做,我会得到建议,让name成为一个引用(以确保它不会每次被复制),并且警告st
class Creature
{
private:
std::string m_name;
public:
Creature(const std::string &name)
: m_name{name}
{
}
};
这导致了clang tidy的建议,即我应该通过值而不是引用传递,并使用std::move
。
如果我这样做,我会得到建议,让name
成为一个引用(以确保它不会每次被复制),并且警告std::move
不会有任何效果,因为name
是一个const
,所以我应该删除它
我不收到警告的唯一方法是将const
一起删除:
Creature(std::string name)
: m_name{std::move(name)}
{
}
这似乎合乎逻辑,因为const
的唯一好处是防止弄乱原始字符串(这不会发生,因为我通过值传递)。
但我继续读:
尽管请注意,在标准库中,移动意味着从对象移动的对象处于有效但未指定的状态。这意味着,在这样一个操作之后,从对象中移出的值只应被销毁或分配一个新值;否则访问它会产生一个未指定的值
现在想象一下这个代码:
std::string nameString("Alex");
Creature c(nameString);
因为nameString
是按值传递的,std::move
只会使构造函数内的name
无效,而不会触及原始字符串。但这有什么好处呢?不管怎样,内容似乎只被复制了一次——如果我在调用m_name{name}
时通过引用传递,如果我在传递它时通过值传递(然后它被移动)。我知道这比按值传递而不使用std::move
(因为它会被复制两次)要好
所以有两个问题:
std::move
而不是通过引用调用m_name{name}
,有什么好处吗std::move
而不是通过引用调用m_name{name}
,有什么好处吗const std::string&
引用是否会存储为数据成员,以后可能会成为悬空引用。而且,在将右值传递给函数时,无需重载std::string&&name
和const std::string&
参数,以避免不必要的副本。传递左值
std::string nameString("Alex");
Creature c(nameString);
对于按值获取参数的函数,将导致一次复制和一次移动构造。将右值传递给同一函数
std::string nameString("Alex");
Creature c(std::move(nameString));
导致两步结构。相反,当函数参数为const std::string&
时,即使在传递右值参数时,也将始终存在副本。这显然是一个优势,只要参数类型移动构造成本低(这是std::string
的情况)
但有一个缺点需要考虑:对于将函数参数分配给另一个变量(而不是初始化它)的函数,推理不起作用:
将导致在重新分配资源之前,
m_name
引用的资源被解除分配。我推荐在现代C++中阅读41项,也可以。<强> > < <强> >不是这里唯一的变量,<强> > < <强> >使两者之间有很大差异。
<>在C++中,我们有这个“成语”,用于在“强”>rValue/String(例如<代码> >亚历克斯字符串文字中构造临时STD::/String 或std::string
的move构造函数
- 传递的左值绑定到
,然后复制到name
m_name
- 传递的右值绑定到
,然后复制到name
m\u name
- 传递的左值被复制到
,然后移动到name
mu name
- 传递的右值被移动到
,然后移动到name
mu name
- 传递的左值绑定到
,然后复制到name
m_name
- 传递的右值绑定到
,然后移动到rname
m\u name
由于移动操作通常比拷贝快,(1)比(0)好,如果你通过了很多临时操作。(2)在拷贝/移动方面是最佳的,但需要代码重复 通过完美转发可以避免代码重复:
/*(3)*/
模板=0
>
生物(T&&name):m_name{std::forward(name)}{
您可以选择约束T
,以限制此构造函数可以实例化的类型的域(如上所示)。C++20旨在简化此操作
在C++17中,prvalues受影响,如果适用,将参数传递给函数时,prvalues会减少复制/移动的数量。传递值和移动方法优于传递-(rv)引用有几个缺点:
- 它会导致生成3个对象
void setName(std::string name) { m_name = std::move(name); }
/* (0) */ Creature(const std::string &name) : m_name{name} { }
/* (1) */ Creature(std::string name) : m_name{std::move(name)} { }
/* (2) */ Creature(const std::string &name) : m_name{name} { } Creature(std::string &&rname) : m_name{std::move(rname)} { }
/* (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)} { }
#pragma pack(push, 1) template<class T> class CopyOrMove{ public: CopyOrMove(T&&t):m_move(&t),m_isMove(true){} CopyOrMove(const T&t):m_reference(&t),m_isMove(false){} bool hasInstance()const{ return m_isMove; } const T& getConstReference() const { return *m_reference; } T extract() && { if (hasInstance()) return std::move(*m_move); else return *m_reference; } void fastExtract(T* out) && { if (hasInstance()) *out = std::move(*m_move); else *out = *m_reference; } private: union { T* m_move; const T* m_reference; }; bool m_isMove; }; #pragma pack(pop)
Creature(CopyOrMove<std::string> name) : m_name{std::move(name.extract())} { }