我正在学习C++,并试着避免养成坏习惯.根据我的理解,clang-tidy包含许多"最佳实践",我尽可能地尽可能地坚持它们(即使我不一定理解为什么它们被认为是好的),但我不确定我是否了解这里推荐的内容.
我在教程中使用了这个类:
class Creature
{
private:
std::string m_name;
public:
Creature(const std::string &name)
: m_name{name}
{
}
};
Run Code Online (Sandbox Code Playgroud)
这导致了一个讽刺的建议,即我应该通过值而不是引用和使用std::move.如果我这样做,我得到的建议做出name的引用(以确保它不会被复制每次),并警告说std::move将不会有任何影响,因为name是const这样,我应该将其删除.
我没有得到警告的唯一方法是const完全删除:
Creature(std::string name)
: m_name{std::move(name)}
{
}
Run Code Online (Sandbox Code Playgroud)
这似乎是合乎逻辑的,因为唯一的好处const是防止弄乱原始字符串(这不会因为我通过值传递而发生).但我在CPlusPlus.com上看到了:
虽然注意到-in标准库移动意味着移动的对象保持有效但未指定的状态.这意味着,在这样的操作之后,移动对象的值应该只被销毁或分配一个新值; 访问它否则会产生一个未指定的值.
现在想象一下这段代码
std::string nameString("Alex");
Creature c(nameString);
Run Code Online (Sandbox Code Playgroud)
因为nameString通过值传递,std::move只会name在构造函数内部无效而不会触及原始字符串.但这有什么好处?看起来无论如何内容只被复制一次 - 如果我在调用时通过引用传递m_name{name},如果我在传递它时传递值(然后它被移动).我知道这比通过值而不是使用std::move(因为它被复制两次)更好.
所以有两个问题:
std::move过度引用和调用是否有任何好处m_name{name}?Vit*_*meo 68
/* (0) */
Creature(const std::string &name) : m_name{name} { }
Run Code Online (Sandbox Code Playgroud)
传递的左值绑定name,然后复制到m_name.
传递的右值绑定name,然后复制到m_name.
/* (1) */
Creature(std::string name) : m_name{std::move(name)} { }
Run Code Online (Sandbox Code Playgroud)
一个通过左值被复制到name,然后移动到m_name.
一个通过右值被移动到name,然后移动到m_name.
/* (2) */
Creature(const std::string &name) : m_name{name} { }
Creature(std::string &&rname) : m_name{std::move(rname)} { }
Run Code Online (Sandbox Code Playgroud)
传递的左值绑定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)} { }
Run Code Online (Sandbox Code Playgroud)
您可以选择约束T以限制此构造函数可以实例化的类型的域(如上所示).C++ 20旨在通过Concepts简化这一过程.
在C++ 17中,prvalues受到保证副本省略的影响,在适用时,它会减少将参数传递给函数时的副本/移动次数.
lub*_*bgr 22
- 我是否正确理解这里发生了什么?
是.
- 使用
std::move过度引用和调用是否有任何好处m_name{name}?
一个易于掌握的功能签名,没有任何额外的重载.签名会立即显示该参数将被复制 - 这样可以避免调用者想知道const std::string&引用是否可以存储为数据成员,以后可能成为悬空引用.当rvalues传递给函数时,不需要重载std::string&& name和const std::string&参数来避免不必要的副本.通过左值
std::string nameString("Alex");
Creature c(nameString);
Run Code Online (Sandbox Code Playgroud)
按值获取其参数的函数会导致一个副本和一个移动构造.将右值传递给相同的函数
std::string nameString("Alex");
Creature c(std::move(nameString));
Run Code Online (Sandbox Code Playgroud)
导致两个移动结构.相反,当函数参数const std::string&为时,即使传递rvalue参数,也总会有副本.这显然是一个优势,只要参数类型移动构造便宜(这是这种情况std::string).
但是有一个缺点需要考虑:推理不适用于将函数参数分配给另一个变量的函数(而不是初始化它):
void setName(std::string name)
{
m_name = std::move(name);
}
Run Code Online (Sandbox Code Playgroud)
将导致m_name在重新分配之前引用的资源的重新分配.我建议阅读Effective Modern C++中的第41项以及这个问题.
| 归档时间: |
|
| 查看次数: |
11541 次 |
| 最近记录: |