C++11 完美转发的使用场景

895 阅读3分钟

参数在传递过程左右值的属性变化

在C++之中的变量有左值与右值两种:其中凡是可以取地址的变量就是左值,而没有名字的临时变量,就是右值

将一个对象中资源移动到另一个对象中的方式,称之为移动语义。在C++11中如果需要实现移动语义,必须使用右值引用。

void func1(int& i) {
    cout << "left value:" << i << endl;
}
void func1(int&& i) {
    cout << "right value:" << i << endl;
}

void func2(int i) {
    func1(i);
}

int main() {

    int i = 9;
    //实参是左值
    func1(i);
    //实参是右值
    func1(99);

    func2(i);
    func2(99);

    return 0;
}

上述代码输出结果为:

left value:9  
right value:99
left value:9  
left value:99 

可以看到调用func1可以正确区分左值和右值;但是func2的输出结果无法区分左值和右值。

那func2的代码该怎么写呢?

//左值版本
void func2(int& i) {
    func1(i);
}
//右值版本
void func2(int&& i) {
    func1(move(i));
}

//函数模板同理
template<typename T>
void func2(T& t) {
    func1(t);
}

template<typename T>
void func2(T&& t) {
    func1(move(t));
}

C++11中,std::move()函数,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,通过右值引用使用该值,实现移动语义。通过move函数可以实现参数在传递的过程中保持左右值的属性。这是一种解决方法。

完美转发

在函数模板中,可以将参数“完美”的转发给其它函数。所谓完美,即不仅能准确的转发参数的还能保证被转发参数的左、右值属性不变。

C++11 标准引入了右值引用和移动语义,所以,能否实现完美转发,决定了该参数在传递过程使用的是拷贝语义还是移动语义。

为了支持完美转发,C++11提供了以下方案:

1)如果函数模板的参数类型为 T&&,C++可既可以接受左值引用,又可以接受右值引用。

2)提供了模板函数 std::forward(参数),用于转发参数,如果参数是一个右值,转发之后仍是右值引用;如果参数是一个左值,转发之后仍是左值引用。

void func1(int& i) {
    cout << "left value:" << i << endl;
}
void func1(int&& i) {
    cout << "right value:" << i << endl;
}

template<typename T>
void func2(T&& t) {
    func1(forward<T>(t));
}

int main() {

    int i = 9;
    //左值
    func1(i);
    //右值
    func1(99);

    func2(i);
    func2(99);

    return 0;
}

输出结果为:

left value:9  
right value:99
left value:9  
right value:99

很明显这种方案的代码比之前的要简洁很多。

普通函数的形参如果是右值引用,是不能接收左值的。模板函数的这种形式(void func2(T&& t)可以,是因为编译器做了特别的处理。

编译器可以区分左值和右值。调用模板函数的时候,它把参数的左右值属性存起来,forward模板函数再读取存起来的信息,然后转发出去。对编译器来说要做到这些很容易。所以forword() 函数模板用于修饰被调用函数中需要维持参数左、右值属性的参数。

但是需要注意模板函数的这种形态(void func2(T&& t) ),既可以接受左值,又可以接受右值。

void func2(int&& t)这种形参不行,前面这个是用模板通用参数定义的,后面这个不是,这个只能接受右值。

在编译其中这行代码第二个参数就是右值没问题,第二个参数是左值就不行,直接报错了,参数列表不匹配。

总结

通过结合右值引用参数和std::forward()函数,可以实现函数模板中参数的完美转发,确保参数的值类别(左值或右值)保持不变,同时减少了不必要的拷贝或移动操作,提高了性能。