按值传递和 std::move 与转发引用

Pass-by-value and std::move vs forwarding reference

提问人:Alex O 提问时间:10/28/2021 最后编辑:Alex O 更新时间:10/28/2021 访问量:836

问:

我经常遇到按值传递和移动的习语:

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;
};

编辑:

建议的问题内容丰富,但没有提到“自动”情况。

C++ 按值 forwarding-reference 传递 const-reference 传递 by-rvalue-reference

评论

0赞 Quimby 10/28/2021
这回答了你的问题吗?pass-by-value-and-move结构是一个不好的成语吗?
1赞 Eugene 10/28/2021
Scott Meyers的“现代C++”教科书对此进行了详细分析。
0赞 Eugene 10/28/2021
@康桓瑋 不,这是一个不同的问题。

答:

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::stringTest2&

评论

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 值将是模棱两可的,而不是硬错误(所以“更好”的错误消息)。Test2template <typename T> Test4(T str);Test3Test4
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 的优势