提问人:Alex O 提问时间:10/28/2021 最后编辑:Alex O 更新时间:10/28/2021 访问量:836
按值传递和 std::move 与转发引用
Pass-by-value and std::move vs forwarding reference
问:
我经常遇到按值传递和移动的习语:
struct Test
{
Test(std::string str_) : str{std::move(str_)} {}
std::string str;
};
但在我看来,在某些情况下,通过常量引用或右值引用可以保存副本。像这样:
struct Test1
{
Test1(std::string&& str_) : str{std::move(str_)} {}
Test1(std::string const& str_) : str{str_} {}
std::string str;
};
或者,也许使用转发引用来避免同时写入两个构造函数。像这样:
struct Test2
{
template<typename T> Test2(T&& str_) : str{std::forward<T>(str_)} {}
std::string str;
};
是这样吗?如果是这样,为什么不使用它呢?
此外,看起来 C++20 允许使用自动参数来简化语法。我不确定在这种情况下的语法是什么。考虑:
struct Test3
{
Test3(auto&& str_) : str{std::forward<decltype(str_)>(str_)} {}
std::string str;
};
struct Test4
{
Test4(auto str_) : str{std::forward<decltype(str_)>(str_)} {}
std::string str;
};
编辑:
建议的问题内容丰富,但没有提到“自动”情况。
答:
3赞
Jarod42
10/28/2021
#1
但在我看来,在某些情况下,通过常量引用或右值引用可以保存副本。
确实如此,但它需要更多的重载(甚至最差的是几个参数)。
按值传递和移动成语(在最坏的情况下)有一个额外的动作。在大多数情况下,这是一个很好的权衡。
也许使用转发引用来避免同时写入两个构造函数。
转发引用有其自身的缺陷:
- 不允许参数的语法,因为没有类型。 是不可能的。
{..}
{..}
Test2 a({5u, '*'}); // "*****"
- 不限于有效类型(需要额外或 SFINAE)。 会在构造函数内部产生错误,而不是在调用站点产生错误(因此错误消息不太清晰,并且 SFINAE 是不可能的)。
requires
Test2 b(4.2f); // Invalid, but `std::is_constructible_v<Test2, float>` is (falsely) true.
- 对于构造函数,它可以优先于复制构造函数(对于非常量 L 值)会产生错误,因为无法从 构造。
Test2 c(a); // Call Test2(T&&) with T=Test2& // instead of copy constructor Test2(const Test2&)
std::string
Test2&
评论
0赞
Alex O
10/28/2021
你能用不太专业的术语详细说明一下这些陷阱吗?
0赞
Alex O
10/28/2021
此外,模板(或自动)语法不应重载
0赞
Jarod42
10/28/2021
添加了一些示例。我希望它更清楚。
0赞
Alex O
10/29/2021
对 Test3/Test4 有什么意见吗?
0赞
Jarod42
10/29/2021
Test3
是 的同轴糖。Test4 用于 ,它按值取参数。唯一的优点是复制非常量 l 值将是模棱两可的,而不是硬错误(所以“更好”的错误消息)。Test2
template <typename T> Test4(T str);
Test3
Test4
1赞
Bob__
10/28/2021
#2
除了 Jarod42 的答案和建议的 dupes(1) 之外,您还可以通过限制模板参数包的有效类型来克服前向引用方法的缺陷。
#include <string>
#include <concepts>
struct Test
{
template<class... Args>
requires std::constructible_from<std::string, Args...>
Test(Args&&... str_)
: str( std::forward<Args>(str_)... )
{}
std::string str;
};
int main()
{
Test a{"So far, so good..."};
Test b{5u, '*'}; // -> "*****"
// Test b({5u, '*'}); // It works too.
Test c{b};
// Test d(4.2f);
// error: no matching constructor for initialization of 'Test'
}
(1) pass-by-value-and-then move 结构是一个不好的成语吗? 以及
pass-by-value 和 std::move 相对于 pass-by-reference 的优势
评论