【问题标题】:std::forward and constructor with non-const reference argumentstd::forward 和带有非常量引用参数的构造函数
【发布时间】:2011-12-02 11:02:03
【问题描述】:

右值引用简介中,提出了完美转发作为将右值 5 转发到具有非常量引用参数的构造函数的理想解决方案。

但是:

#include <memory>
#include <iostream>
#include <utility>

template <class T, class A1>
std::shared_ptr<T> factory(A1&& a1) {
   return std::shared_ptr<T>(new T(std::forward<A1>(a1)));
}

class X {
public:
    X(int& i){
        std::cout<<"X("<<i<<")\n";
    }
};


int main() {
    std::shared_ptr<X> p = factory<X>(5);
}

在 XCode 4.2 和 G++ 4.6.1 中使用 no known conversion from int to int&amp; 失败,而:

template <class T, class A1>
std::shared_ptr<T> factory(A1&& a1) {
   return std::shared_ptr<T>(new T(/*no forwarding*/a1));
}

编译。我做错了什么?

【问题讨论】:

    标签: c++ c++11 perfect-forwarding


    【解决方案1】:

    您不能将右值绑定到非 const 左值引用。该文章不建议为此使用完美转发,因为那是不可能的。完美转发将左值作为左值,将右值作为右值转发:

    在这里,forward 保留了参数的左值/右值 被传递到工厂。如果将右值传递给工厂,则 rvalue 将在 forward 的帮助下传递给 T 的构造函数 功能。类似地,如果一个左值被传递给工厂,它是 作为左值转发给 T 的构造函数。

    由于您示例中的构造函数只接受左值,因此您只能将左值传递给工厂函数。传递右值会将其作为右值转发,这将是错误的,因为无法将右值传递给该构造函数。

    【讨论】:

      【解决方案2】:

      提出了完美转发作为将右值 5 转发到具有非常量引用参数的构造函数的理想解决方案。

      我不认为完美的转发意味着那个。这篇文章,如果它是正确的,即使是远程也不能暗示。

      相反,这意味着它可以将右值引用作为右值转发,因此可以调用 move-constructor 或采用右值引用的构造函数/函数。

      所以你应该试试这个:

      class X {
      public:
          X(int& i){
              std::cout<<"X("<<i<<")\n";
          }
      
          //this is a constructor which takes rvalue references
          X(int&& i){ 
              std::cout<<"X("<<i<<")\n";
          }
      };
      

      也就是说,从factory 开始,应该调用第二个构造函数,而不是你写的那个。

      顺便说一句,在这种情况下,构造函数没有多大意义,因为参数类型int 是基本类型。

      作为参数类型的右值引用用于定义管理资源的类的移动构造函数和移动分配。如果用户定义的类不管理资源,那么移动语义就没有意义。

      【讨论】:

      • in this case, move-constructor doesn't makes sense much+1
      【解决方案3】:

      暂时忽略右值引用,并假装这是允许的:

      void modify_int(int& i)
      {
          i = 1;
      }
      
      void foo(int& x)
      {
          modify_int(x); // okay, modify_int references x
      }
      
      int i = 7;
      foo(i); // makes i = 1
      
      // illegal in standard C++, cannot bind a temporary to a non-const reference
      foo(5); // makes the temporary integer equal to 1
      

      您可以看到临时对象被修改了,这很好。然而,这种绑定在 C++ 中是非法的,因为它通常是不受欢迎的(它读起来好像 5 被更改为 1,毕竟)。

      所有 rvalue-references 所做的只是启用临时值与引用的绑定,但很安全,因为我们知道我们正在处理一个应该被视为临时值的值:

      void modify_int(int& i)
      {
          i = 1;
      }
      
      void foo(int&& x)
      {
          modify_int(x); // okay, modify_int references x
      }
      
      int i = 7;
      foo(std::move(i)); // makes i = 1 (std::move makes it an rvalue)
      
      // legal in C++11, temporary is bound to rvalue-reference
      foo(5); // makes the temporary integer equal to 1
      

      请注意,在这个版本的foo 中,传递给modify_int 仍然非常好。一旦进入函数,它是一个右值引用而不是一个左值引用这一事实是无关紧要的:我们仍然有一个对象要引用。 Forwarding is used in templates to preserve the value category:

      void test(int& i) {} // lvalue version of test
      void test(int&& i) {} // rvalue version of test
      
      template <typename T>
      void foo(T&& x)
      {
          // if x was an lvalue, forward does nothing;
          // if x was an rvalue, forward std::move's it 
          test(std::forward<T>(x)); 
      }
      
      int i = 7;
      foo(i); // calls lvalue version of test
      
      foo(5); // calls rvalue version of test
      

      您没有转发的代码类似于我回答中的第二个 sn-p。一旦进入factory 函数,a1 就只是一个常规的左值,并且可以很好地绑定到构造函数引用。但是通过转发,它又变成了一个右值(因为factory(5)用一个右值调用它),它无法绑定到左值引用,从而导致错误。

      【讨论】:

        猜你喜欢
        • 2014-04-15
        • 2015-08-21
        • 2017-01-31
        • 2015-08-27
        • 2023-03-20
        • 2022-01-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多