7

A Brief Introduction to Rvalue References中,提出了完美转发作为将 rvalue 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 ans G++ 4.6.1 with 失败no known conversion from int to int&,而:

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

编译。我做错了什么?

4

3 回答 3

5

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

在这里,forward 保留了传递给工厂的参数的左值/右值。如果一个右值被传递给工厂,那么一个右值将在 forward 函数的帮助下被传递给 T 的构造函数。同样,如果将左值传递给工厂,它会作为左值转发给 T 的构造函数。

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

于 2011-12-02T11:10:58.070 回答
5

完美转发被提议作为将右值 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";
    }
};

也就是说, fromfactory应该调用第二个构造函数,而不是您编写的那个。

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

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

于 2011-12-02T11:08:47.493 回答
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,传递 tomodify_int仍然非常好。一旦进入函数,它是一个右值引用而不是一个左值引用这一事实是无关紧要的:我们仍然有一个对象要引用。模板中使用转发来保留值类别

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

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

于 2011-12-02T11:18:18.503 回答