引用
指针与引用
引用必须要初始化。
指针会根据编译器不同而变化,32位4字节,64位8字节
引用根据被引用的数据类型变化
int*& 指针的引用 以指针来判断。
右值引用 c++11
- 左值:可以长时间保存,可以存在于=左边的值,可以取地址;
- 右值:临时值,不能存在于=左边的值,不可以取地址。
左值引用,实际上是取地址赋给新的变量。必须初始化。
常引用,用于引用部分右值,不可进行更改。实际上是使用一个临时变量与一块临时内存进行存储,必须初始化。可以引用左与右。
右值引用原理相近,临时内存的地址无法获取,但是可以对临时内存里面的内容进行修改。
1 | int&& v1 = 10; |
右值引用是C++11新特性,之所以引入右值引用,是为了提高效率。如下面所示:
1 | class A |
这里会导致大量得调用A得构造函数,不考虑编译优化,原本执行如下:
1 | createA(100),执行A(100)调用A(size_t)构造函数一次; |
从上面可以看出有大量得构造、析构调用 ,但是我们做的工作无非就是临时构造一个A(100)给func使用而已。那么可否将临时A(100)始终一份给到func使用呢?答案就是右值引用。如下:
1 | class A |
我们将临时A(100)强制转换为了右值引用,同时func形参也是右值引用,也就是将临时对象延长到了func中,中间避免了其他构造和析构调用,提高了效率。
注意到我们将A得拷贝构造函数去掉了,因为已经用不到。如果原版写法,去掉拷贝构造函数会崩溃,因为会自动调用默认拷贝构造函数,是浅拷贝,中间临时对象会提前删除公共内存,后面对象再次释放是就会重复删除内存导致崩溃。
这就是移动。它可以让你将一个对象的资源(如内存、文件句柄等)从一个临时的右值转移给另一个对象,而不需要进行深拷贝这样可以提高性能,避免不必要的内存分配和释放
std::move可以转换左值引用为右值引用。实现原理实际上就是强制转换
1 | int main() |
1 | int main() |
std::unique_ptr
通用引用
通用引用就是根据接受值类型可以自行推导是左值引用还是右值引用。
如果声明变量或参数具有T&&某种推导类型的类型 T,则该变量或参数为通用引用,否则就是右值引用(无法传入左值)。
也就是传入的参数在编译时需要推导,如果不需要推导,则不是通用引用。如下:
1 | template<typename T> |
因为在编译print之前print中的参数已经由B
1 | template<typename T> |
因为print时函数模板形参和类模板形参类型时独立的,故在编译print时是需要推导的,故Arg&&为通用引用。
引用折叠
引用虽然形式上是右值引用,但是却可以接受左值,这是怎么实现的呢?这就是引用折叠。
1 | template<typename T> |
print(a)时,因为a为左值,会被推导成print(int& &&t)形式,int& &&t 会被折叠为int &,所以最终形式为print(int &)。(左值被推导为左值引用)
print(9)时,为9为右值,所以被推导为print(int&& &&)形式,而int&& &&会被折叠为int&&,所以最终形式为print(int&&)。(右值被推导为右值引用)
引用类型只有两种,所以折叠形式就是4中,为:T& &,T& &&,T&& &,T&& &&。引用折叠规则概况为两种:
T&& &&折叠为T&&;
其他折叠为T&.
完美转发
通用引用既可以接受左值也可以接受右值,但是通用引用本身是左值。如果在函数模板中继续传递该值给其他函数,势必会改变该值的属性,即都为左值引用。
使用std::forward
原理是使用了引用折叠。具有推导类型的T&&转换会进行引用折叠。而int&&类型是确定的,不能进行折叠。
有两套,传入的为左或右,用右值进行强制类型转换,左右转化为左,右右转化为右