【问题标题】:C++ pass parameter by rvalue reference if possible, otherwise copy the lvalue reference如果可能,C++ 通过右值引用传递参数,否则复制左值引用
【发布时间】:2017-03-23 13:36:42
【问题描述】:

使用右值引用,可能会省略许多冗余副本,但这似乎需要我多次编写相同的函数(一个用于右值引用,一个用于const 左值引用)。但是标准库似乎只需要声明一次函数。

例如:

#include <iostream>
#include <tuple>

void foo(int&& x){
    x = 2;
}

int main()
{
    int x = 1;
    foo(x); // compile error
    std::make_tuple(x); // ok
    std::cout << x << std::endl;
}

调用foo(x) 是一个编译错误,因为我无法将int 隐式转换为int&amp;&amp;。但我很困惑为什么std::make_tuple 会起作用。 reference 表示它只接受右值引用参数。当传递给它的值是一个 ravlue 引用时,它似乎也不会进行复制,但在我上面的示例中使用时,它会复制(正如大多数人所期望的那样)。

我怎样才能让foo 像这样工作?

【问题讨论】:

    标签: c++ function c++11 templates rvalue-reference


    【解决方案1】:

    引用说它只接受右值引用参数。

    不,这是forwarding reference,根据传入参数的值类别,既可以作为左值引用,也可以作为右值引用。

    我怎样才能让foo 像这样工作?

    声明转发引用的要点是(1)需要进行类型推导,也就是说这里需要将foo做一个函数模板; (2) 参数x 与模板参数T 的精确形式为T&amp;&amp;。例如

    template <typename T>
    void foo(T&& x){
        x = 2;
    }
    

    然后

    int x = 1;
    foo(x);   // lvalue passed, T is deduced as int&, parameter's type is int&
    foo(1);   // rvalue passed, T is deduced as int, parameter's type is int&&
    

    注意std::make_tuple 也是如此,即使它使用模板参数包。最好记住,即使转发引用看起来也像右值引用,但它们是不同的东西。

    顺便说一句:std::forward 通常与转发引用一起使用以保留参数的值类别,例如转发给其他函数时。

    【讨论】:

    • 感谢您的详细说明。如果不是intfoo 需要接受std::string(或它的一些参考),那么将foo 作为此转发行为的模板是否很好?任何其他不能分配给std::string 的东西都没有意义,也不应该传递给foo
    • @Bernard 在这些情况下,您使用std::enable_ifT 约束为std::string
    • @Bernard 这取决于你如何使用foo里面的参数;如果有错误传递给foo,您可能会收到编译错误。无论如何,转发引用被设计成能够接受几乎任何类型的参数;因此,如果 foo 仅适用于 std::string,则转发引用可能会过度杀伤。另一方面,如果您需要更通用的类型,例如不仅是std::string,还有字符串文字(const char[]),那么转发引用是合适的。
    • @songyuanyao 说得好,最好接受任何可分配给std::string 的内容,包括std::string_view (C++17)。
    • @Bernard 是的。取决于你的使用情况,不仅可以省略复制,还可以移动;参数可以直接使用,不需要每次都构造一个(临时的)std::string
    【解决方案2】:

    差异是&amp;&amp; 运算符在与模板参数一起使用时相当微妙的重载的结果:

    template<typename foo>
    void bar(foo &&baz)
    

    这不是右值引用。它是一个转发参考。当模板被解析时,baz 将成为左值或右值引用,具体取决于调用情况。

    这就是为什么您会看到 C++ 库提供了看似单一的模板,在左值和右值上下文中都可以使用。

    但是转发引用只发生在模板中。在非模板声明中:

    void bar(int &&baz)
    

    这始终是一个右值引用,并且只能在右值上下文中使用。

    【讨论】:

      【解决方案3】:

      std::make_tuple 之所以有效,是因为该函数实际上并不像foo 那样采用右值引用。 std::make_tuple 采用T&amp;&amp; 形式的对象,并且由于T 是模板类型,因此它使其成为转发引用,而不是右值引用。转发引用可以绑定到左值和右值,而右值引用只能采用右值。要使 foo 与您需要的相同

      template<typename T>
      void foo(T&& bar)
      {
          bar = 2;
      }
      

      【讨论】:

        【解决方案4】:

        这对你有用吗?

        #include <iostream>
        #include <tuple>
        
        void foo(int&& x){
            std::cout<<"rvalue"<<std::endl;
            x = 2;
        }
        
        void foo(int& x){
            std::cout<<"lvalue"<<std::endl;
            x = 2;
        }
        
        int main()
        {
            int x = 1;
            foo(x); // no compile error anymore
            foo(std::move(x)); // now r-value is being used        
            std::make_tuple(x); // ok
            std::cout << x << std::endl;
        }
        

        【讨论】:

          猜你喜欢
          • 2019-08-22
          • 2020-01-23
          • 1970-01-01
          • 2020-03-08
          • 2014-09-09
          • 1970-01-01
          • 2013-08-01
          • 1970-01-01
          • 2017-11-26
          相关资源
          最近更新 更多