按值传递和 std::move 与转发引用
Pass-by-value and std::move vs forwarding reference
我经常遇到按值传递和移动成语:
struct Test
{
Test(std::string str_) : str{std::move(str_)} {}
std::string str;
};
但在我看来,在某些情况下,传递 const 引用或右值引用都可以保存一个副本。类似于:
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;
};
编辑:
建议的问题提供了信息,但没有提及“汽车”案例。
But it seems to me that passing by either const reference or rvalue reference can save a copy in some situations.
确实如此,但它需要更多的重载(甚至最糟糕的是有几个参数)。
按值传递和移动成语有(最坏的情况)多走一步。大多数时候这是一个很好的权衡。
maybe using a forwarding reference to avoid writing both constructors.
转发引用有其自身的缺陷:
- 不允许参数使用
{..}
语法,因为 {..}
没有类型。
Test2 a({5u, '*'}); // "*****"
不可能的。
- 不限于有效类型(需要额外的
requires
或 SFINAE)。
Test2 b(4.2f); // Invalid, but `std::is_constructible_v<Test2, float>` is (falsely) true.
会在构造函数内部产生错误,而不是在调用点(因此错误消息不太清楚,SFINAE 不可能)。
- 对于构造函数,它可以优先于复制构造函数(对于非常量左值)
Test2 c(a); // Call Test2(T&&) with T=Test2&
// instead of copy constructor Test2(const Test2&)
会产生错误,因为 std::string
不能从 Test2&
. 构造
添加到 by 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) Is the pass-by-value-and-then-move construct a bad idiom? 和
我经常遇到按值传递和移动成语:
struct Test
{
Test(std::string str_) : str{std::move(str_)} {}
std::string str;
};
但在我看来,在某些情况下,传递 const 引用或右值引用都可以保存一个副本。类似于:
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;
};
编辑:
建议的问题提供了信息,但没有提及“汽车”案例。
But it seems to me that passing by either const reference or rvalue reference can save a copy in some situations.
确实如此,但它需要更多的重载(甚至最糟糕的是有几个参数)。
按值传递和移动成语有(最坏的情况)多走一步。大多数时候这是一个很好的权衡。
maybe using a forwarding reference to avoid writing both constructors.
转发引用有其自身的缺陷:
- 不允许参数使用
{..}
语法,因为{..}
没有类型。
不可能的。Test2 a({5u, '*'}); // "*****"
- 不限于有效类型(需要额外的
requires
或 SFINAE)。
会在构造函数内部产生错误,而不是在调用点(因此错误消息不太清楚,SFINAE 不可能)。Test2 b(4.2f); // Invalid, but `std::is_constructible_v<Test2, float>` is (falsely) true.
- 对于构造函数,它可以优先于复制构造函数(对于非常量左值)
会产生错误,因为Test2 c(a); // Call Test2(T&&) with T=Test2& // instead of copy constructor Test2(const Test2&)
std::string
不能从Test2&
. 构造
添加到
#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) Is the pass-by-value-and-then-move construct a bad idiom? 和