为什么要有右值引用,右值引用出现前程序员们的困境:
在右值引用出现以前,想要把一块内存空间里的内容放到另一块内存空间,只能再开辟一块内存,然后将原来内存里的内容复制到新开辟的内存里,然后再把原来的内存清理掉,这样操作有很大的空间分配和清理的消耗 ,还容易产生内存碎片
右值引用的出现
右值引用出现使得移动构造和移动赋值得以实现
移动就是指针所有权的转移,原来的 指针1 指向了一块内存,现在再新建一个指针 指针2 指向这块内存,然后让原来的指针1指向空,就完成了一次资源转移,从而实现了右值引用的 终极目标 :减少复制,提升性能
移动构造没有内存复制,取而代之的是简单的指针替换,没有新内存的申请和旧内存的释放
cpp
CharBuffer::CharBuffer(CharBuffer&& temp):size(temp.size),buf(temp.buf)
{
cout << "移动构造函数" << endl;
temp.buf = nullptr;
temp.size = 0;
}
不就是指针转移嘛,反正都是代码实现,为什么拷贝构造里不能进行一样的操作来实现移动语义呢?
因为拷贝构造函数的传参是const无法把原指针指向空,于是会出现两个对象指向同一片内存空间的局面,这时候就容易造成一片空间两次析构的危险
cpp
CharBuffer::CharBuffer(const CharBuffer& temp) :size(temp.size), buf(new char[size])
{
cout << "拷贝构造函数" << endl;
memcpy(buf, temp.buf, size);
}
如果没有自己定义的移动构造函数,通过右值传参其实也是调用的拷贝构造,因为拷贝构造函数的参数是 const 加上const以后,传参就变成了常量,反正也无法被改变,编译器就会自动做出优化,使右值匹配到拷贝构造函数
cpp
{
CharBuffer test1;
CharBuffer test2(move(test1));
}
同理,传参是 const的都可以接收右值
此外右值往往都是一些临时变量 ,比如函数返回值,当一个对象的生命周期即将结束,而我们又希望在后续继续使用这个对象,并且又不想用拷贝,因为会产生复制开销 ,此时就可以通过右值移动来实现延长临时对象的生命周期(将亡值)
std::move()
通过move源代码可以看到
cpp
template <class _Ty>
remove_reference_t<_Ty>&& move(_Ty&& _Arg) {
return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}
move其实就是把传进来的变成右值引用返回出去(传左值引用就会通过remove_reference移除掉引用变回原本的类型)
所以通过move给一个对象赋值以后如果在对象里没有特别处理的话,原来的对象其实是可以继续用的
就像下面这段代码,是不会报错的,但是会有提醒test1是已经被移动过的对象
cpp
{
//错误示范
CharBuffer test1;
CharBuffer test2(move(test1));
auto test3 = test1;
cout << test3.size << endl;
}
所以大家其实都是在遵守move结束后不再使用原来的已经被移动了的对象的君子约定,这样更贴合移动的语义,即原对象已经被移动到新对象里,其实原对象还是可以继续使用的(但不要这样做!)
通过forward和万能引用实现完美转发(主要是通过引用折叠推导出是左值引用还是右值引用)
无论一个函数的实参是左值还是右值,他的形参都是左值,所以右值传进函数以后会变成左值,
就像这个里面,不管传进去的是左值还是右值,在TestFunc1里面形参都会变成左值然后调用到左值引用
cpp
void TestFunc2(const string& temp) {
cout << "左值引用" << endl;
}
void TestFunc2(string&& temp) {
cout << "右值引用" << endl;
}
void TestFunc1(string& temp)
{
TestFunc2(temp);
}
void TestFunc1(string&& temp)
{
TestFunc2(temp);
}
int main(int argc, char* argv[])
{
TestFunc1(string("Jolly"));
cout << "==============" << endl;
string test1("Jolly");
TestFunc1(test1);
getchar();
return 0;
}
此时就需要实现完美转发来解决
cpp
void TestFunc1(string& temp)
{
TestFunc2(static_cast<const string&>(temp));//方法1显式强制转换
TestFunc2(std::forward<const string&>(temp));//方法2 forward万能引用模板实现完美转发
}
void TestFunc1(string&& temp)
{
TestFunc2(static_cast<string&&>(temp));
TestFunc2(std::forward<string&&>(temp));
}
通过forward源代码可以看到forward就是把传入进来的都先去引用变回原来的类型,然后经过万能引用推导出应该返回什么类型的引用
cpp
template <class _Ty>
_Ty&& forward(remove_reference_t<_Ty>& _Arg) {
return static_cast<_Ty&&>(_Arg);
}
template <class _Ty>
_Ty&& forward(remove_reference_t<_Ty>&& _Arg) {
return static_cast<_Ty&&>(_Arg);
}
同时也可以通过万能引用模板TestFun1函数换模板函数
cpp
template<class T>;
void TestFunc1(T&& temp)//这是万能引用,不是右值引用哦
{
TestFunc2(std::forward<T>(temp);
}