理解完美转发(perfect forwardindg)

-- TOC --

Reference Collapsing

当引用指向引用的时候,这里的引用可以是lvalue也可以是rvalue,最终的类型会有部分被折叠(collapsing)到一起,具体规则如下(注意地址符之间的空格):

Before After
A& & A&
A& && A&
A&& & A&
A&& && A&&

只有全部为右值引用的情况下才会变为右值引用。

Universal Reference

如下定义的模板参数,被称为universal reference:

template<typename T>
void func(T &&t);

结合Reference Collapsing规则,当用lvalue调用func时,T为左值类型(这是唯一的一个类型推导出T为左值类型的情况),当用rvalue调用func时,T就为T

直觉:parameter pack也可以这样表达:Args&&... args

既然是deduction,就不能指定类型!例如std::move的使用。

Perfect Forwarding

当一个右值引用传入函数时,它就有了名字,所以继续使用它,或者调用其他函数需要它时,根据C++标准的定义,这个参数变成了一个左值。那么,编译器永远不会调用接下来函数的右值版本,这在一些情况下,会造成拷贝。为了解决这个问题 C++11引入了完美转发,调用std::forward返回的值,若原来是一个右值,那么返回的就是一个右值,否则为一个左值。这样的处理,完美地转发了原有参数的左右值属性,不会造成一些不必要的拷贝。

#include <iostream>
#include <string>
using namespace std;


int main(){
    string a{"abcde"};
    // ra类型是a的rvalue
    string &&ra { std::move(a) };
    // 直接使用ra,触发copy
    string b { ra };
    // 输出:abcde
    cout << a << endl;
    // 使用ra的原始类型,a的rvalue,触发move
    string c { std::forward<string>(ra) };  //这是错误的用法,见下面!
    // a已经是空值,无输出
    cout << a << endl;
    return 0;
}

另一个示例:

#include <iostream>
#include <string>
using namespace std;


template<typename T>
void print(T &t){
    cout << "lvalue\n";
}


template<typename T>
void print(T &&t){ // Template Rvalue Argument Deduction
    cout << "rvalue\n";
}


template<typename T>
void test(T &&t){
    // directly to use t,always lvalue
    print(t);
    // depends t at the caller
    print(forward<T>(t));
    // always rvalue
    print(move(t));
}


int main(){
    test(1);
    cout << "----\n";
    int x = 1;
    test(x);
    cout << "----\n";
    test(forward<int>(x));
    return 0;
}

输出:

lvalue
rvalue
rvalue
----
lvalue
lvalue
rvalue
----
lvalue
rvalue
rvalue

注意一个细节:main中的test(forward<int>(x)),x变成了rvalue!我理解,这是不正确的forward用法,forward中的参数,应该是模板参数。因此,此文中间那段测试代码,是错误的。

本文链接:https://cs.pynote.net/sf/c/cpp/202301181/

-- EOF --

-- MORE --