c++模板的引用类型参数折叠问题解释

右值变量只有内容,没有承载这个内容的实体,他表示一个数据信息,你不能像修改左值那样去修改右值变量,不能去取右值变量的地址(但是右值实际上是不是也像左值变量那样也存储在栈地址中我还不清楚)

右值引用是右值变量的别名,左值引用是左值变量的别名

对左值变量而言,它是存在实体的,对一个基本内置类型左值变量A使用move函数(或者调用他的移动运算符/移动构造函数)来初始化另一个同类变量B,相当与把A的内容剪切到B上,

注意B并不是接管了A的内存地址(从下面的结果看出2者并不在同一地址),而是“窃取”了A的内容(至于std::move的实现细节目前没有公开),这样就省去了复制构造的操作,比复制构造有更高的效率

对于我们自定义的类型,在使用一个对象A去构造另一个对象B,而又不再需要原对象A的场景里(因为移动构造会破坏掉源对象),我们可以自定义这个类的移动构造函数,移动操作并没有规定必须是把一个地址的内容移动到另一个地址,

我们自定义的移动构造可以是接管源对象的地址来达到“移动”的目的(或者对类内每个内置类型成员都调用std::move操作)

#include <iostream>

using namespace std;
int main(){
    string str1("hello");
    cout<<"addr str1: "<<&str1<<endl;
    string str2 = move(str1);
    cout<<"str2: "<<str2<<"   addr str2: "<<&str2<<endl;
    cout<<"addr str1 after: "<<&str1<<endl;
    cout<<"str1: "<<str1<<endl;
    return 0;
}

/* result:
addr str1: 0x7fffffffda10
str2: hello   addr str2: 0x7fffffffda30
addr str1 after: 0x7fffffffda10
str1: 
*/

 

 

template<typename T> void f1(T&);   

实参可以是左值、const类型的左值,不能是右值。

f1(i);   // 正确,i是int型,T是int

f1(c);  // 正确,i是const int 型,T是const int

f1(5);  // 错误

 

template<typename T> void f1(const T&);    

实参可以是左值、const类型的左值、右值。

f1(i);  // 正确,i是int型,T是int

f1(c);  // 正确,c是const int 型,T是int

f1(5);  // 正确,5是int&&,T是int

 

template<typename T> void f1(T&&);   

我们可能认为给这种情况传递一个左值给f1是不合法的,但实际上这是合法的,这种情况的实参可以是左值(或左值引用)(无论有无const)、右值(或右值引用)。

1. 当实参是右值(或右值引用)的时候,T被推断为X

f1(5);  // 正确,5是int&&型,T是 int

2. 例外规则①:当模板参数是右值(T&&)而实参是左值(或左值引用),也合法,此时T被推断为X&

f1(i);   // 正确,i是int型,按照上面的规则,T是 int&

 

第1种情况我们很容易理解,因为类型完全匹配。

对于第2种情况,根据例外规则①,把T代入,这个f1模板被实例化为 f1(int& &&) ,它的参数是引用的引用,那为啥我们实际传入的是 int& 咧?对于这种情况,实际上是c++有第2条例外规则:引用折叠。

例外规则②:引用折叠(当出现引用的引用类型变量时就会发生引用折叠)

X& &、X& &&、X&& & 这三种等价折叠为 X&

X&& && 等价折叠为 X&&

所以对于  f1(int& &&) ,它等价于 f1(int &),因此我们传入 int& 的实参是可以的。

 

另一个引用折叠的例子: std::forward()  (c++ primer P614)

为了解决在函数模板中调用其他函数,需要将模板参数转发给这些函数,在传参过程中的const、左值/右值引用属性丢失的问题,使用右值模板参数 + std::forward的方式来传递参数:

templcate<typename T>

tem_func(T &&arg){               // 右值引用使得T保留了实参的 const、左值/右值引用属性

    func(std::forward<T>(arg));  //  无论T是什么类型,std::forward(arg)都返回 T &&

}

例如:

tem_func(5);  // arg类型为int &&,则T类型为int,则std::forward<T>(arg) 是把arg转为int &&,和实参类型保持一致

tem_func(i);  // i类型为int &,则T类型为int &(例外规则① ),则std::forward<T>(arg) 是把arg转为int & &&,也就是 int & (引用折叠),和实参类型也保持一致

至于const属性,由右值模板类型参数来保持

 

emplace_back是直接指定在vector的末尾上调用构造函数构造新元素

而push_back是在一块临时栈内存地址中构造一个临时元素,然后再调用移动构造函数来构造vector的末尾元素,所以实际上是调用了2次构造函数

posted @ 2023-05-27 17:49  大黑耗  阅读(58)  评论(0编辑  收藏  举报