传递值和std:的优点:通过引用传递
Advantages of pass-by-value and std::move over pass-by-reference
我现在正在学习C++,尽量避免养成坏习惯。据我所知,clang整洁包含了许多"最佳实践",我尽可能地坚持它们(尽管我不一定理解为什么它们被认为是好的),但我不确定我是否理解这里的建议。
我使用了教程中的这个类:
class Creature
{
private:
std::string m_name;
public:
Creature(const std::string &name)
: m_name{name}
{
}
};
这导致clang-time建议我应该传递值而不是引用,并使用std::move
。如果我这样做了,我会得到建议,将name
作为参考(以确保它不会每次都被复制),并警告std::move
不会有任何效果,因为name
是const
,所以我应该删除它。
我没有得到警告的唯一方法是完全删除const
:
Creature(std::string name)
: m_name{std::move(name)}
{
}
这似乎是合乎逻辑的,因为const
的唯一好处是防止干扰原始字符串(这不会发生,因为我传递了值)。但我在CPlusPlus.com上读到:
尽管注意,在标准库中,移动意味着移动的from对象处于有效但未指定的状态。这意味着,在这样的操作之后,移动的对象的值只应被销毁或分配一个新的值;否则访问它会产生一个未指定的值。
现在想象一下这个代码:
std::string nameString("Alex");
Creature c(nameString);
因为nameString
是通过值传递的,所以std::move
只会使构造函数内的name
无效,而不会接触原始字符串。但这样做的好处是什么?内容似乎只复制了一次——如果我在调用m_name{name}
时通过引用传递,如果我在传递它时通过值传递(然后它被移动)。我知道这比传递值而不使用std::move
要好(因为它会被复制两次)。
所以有两个问题:
- 我是否正确理解了这里发生的事情
- 与通过引用传递和仅调用
m_name{name}
相比,使用std::move
有什么好处吗
/* (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)优于(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旨在通过Concepts来简化这一过程。
在C++17中,prvalues受到保证的副本省略的影响,如果适用,这将减少向函数传递参数时的副本/移动数量。
- 我是否正确理解了这里发生的事情
是。
- 与通过引用传递和仅调用
m_name{name}
相比,使用std::move
有什么好处吗
一个易于掌握的函数签名,无需任何额外的重载。签名会立即显示参数将被复制-这使调用方不必怀疑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
就是这样)。
但也有一个缺点需要考虑:推理不适用于将函数参数分配给另一个变量(而不是初始化它)的函数:
void setName(std::string name)
{
m_name = std::move(name);
}
将导致m_name
所引用的资源在重新分配之前被解除分配。我建议阅读有效的现代C++中的第41项以及这个问题。
你如何传递并不是这里唯一的变量,你传递的内容决定了两者之间的巨大差异。
在C++中,我们有各种类型的值类别,这种"习惯用法"适用于传递右值(如"Alex-string-literal-that-constructs-temporary-std::string"
或std::move(nameString)
)的情况,这会导致生成0个副本的std::string
(对于右值参数,该类型甚至不必是可复制构造的),并且只使用std::string
的移动构造函数。
一些相关的问答;A.
传递值和移动方法相对于传递-(rv)参考有几个缺点:
- 它会导致生成3个对象,而不是2个
- 按值传递对象可能会导致额外的堆栈开销,因为即使是常规字符串类通常也比指针大至少3或4倍
- 参数对象的构建将在调用方完成,导致代码膨胀
- 使用运算符 [] 引用 std::vector 上最后一个元素时出现问题<>
- 引用 std::any 或 not_yet_in_std::whatever 的惯用方式是什么?
- 引用 std::shared:ptr 以避免引用计数
- 将双指针作为参数传递给需要引用 std::vector <double>的函数
- 引用 std::cout 会导致段错误
- 无法通过引用 std::exception 来捕获从 std::exception 派生的类
- 为什么 std::runtime_error 的 c'tor 经常引用 std::string?
- 我可以在循环中引用 std::string 的一部分并将其放在外面吗?
- 引用 std::atomic <bool>的已删除函数错误
- 如何从对象中获取对元组尾部的引用 std::tuple<Head,Tail...>
- 当我编译引用 std::ostream 时,我有一个奇怪的错误弹出
- 错误 C2062:引用 std::map 时键入意外
- 设置 std::function 变量以引用 std::sin 函数
- 指针/引用 std::unordered_set 中的元素
- 迭代器引用 std::vector<std::unique_ptr<T>>
- C++:引用std::map中的计数值;std::multimap是更好的选择吗
- 未定义的引用std::对
- 引用std::basic_string特化string与自定义分配器作为std::string的常量对象,没有开销
- 使用c++,未定义引用std:: Makefile
- 常量引用std::vector