💂 个人主页:pp不会算法^ v ^
🤟 版权: 本文由【pp不会算法v】原创、在CSDN首发、需要转载请联系博主
💬 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦
概念
左值:能够用取址符号取址的
右值:不能用取址符号取址的,在c++11中右值包括将亡值和纯右值,例如非引用返回的临时变量,计算表达式的临时变量,字面量,lambda表达式都是纯右值;T&&返回值函数返回的值,将要被移动的对象,std::move的返回值,转换为T&&的类型的转换函数的返回值左值引用:对左值的引用 右值引用:对右值得引用 左值引用和右值引用都必须就地初始化,他们都不拥有引用对象的内存而只是引用对象的一个别名
右值引用能够延长右值的生命周期,只要右值引用存在那么它所引用得右值将会一直存在 万能引用:const T&
常引用可以接受左值,右值,常量左值,常量右值
左值转化为右值:std::move
编译器将已命名的右值引用视为左值,将未命名的右值引用视为右值
universal引用
当存在类型推导或者auto关键字的时候T&&属于universal 引用,参数如果是左值那么就是左值,如果是右值就是右值
template
void func(T&&t){}
template
void func(std::vector&&t){} 这里就是右值引用,因为调用的时候T已经确定了
template
void func(const T&&t){} 这里就是右值引用,因为只有T&&类型未定义的时候才是universal 引用不然就是简单的右值引用
引用折叠:
universal 引用存在引用折叠
折叠规则:
1、右值引用和右值引用得折叠得到的是右值引用
2、所有其他类型的折叠得到的结果属于左值引用
右值引用优化代码
Class A{
A()
{
std::cout<<"construct..."<<std::endl;
}
A(const A&a)
{
std::cout<<"copy construct..."<<std::endl;
}
~A()
{
std::cout<<"desconstruct..."<<std::endl;
}
};
A getA()
{
return A();
}
int main()
{
A a=getA();
return 0;
}
在禁止了编译器得优化选项之后的运行结果为:
construct… // return A();中A()引起的构造
copy construct… //return A()构造的A对象赋值给返回值对象
desconstruct…//return A()构造的A对象的析构
copy construct…//getA()的返回值给a赋给、值所引发的复制构造
desconstruct…//getA()返回值对象的析构
desconstruct…//a的析构
Class A{
A()
{
std::cout<<"construct..."<<std::endl;
}
A(const A&a)
{
std::cout<<"copy construct..."<<std::endl;
}
~A()
{
std::cout<<"desconstruct..."<<std::endl;
}
};
A getA()
{
return A();
}
int main()
{
A&& a=getA();
return 0;
}
在禁止了编译器得优化选项之后的运行结果为:
construct… // return A();中A()引起的构造
copy construct… //return A()构造的A对象赋值给返回值对象
desconstruct…//return A()构造的A对象的析构
desconstruct…//getA()返回值对象的析构
观察结果可以发现:减少了一次拷贝
深浅拷贝,以及优化策略,移动语义
浅拷贝:简单的赋值,对于堆内存也是简单的赋值指针
深拷贝:对于堆内存需要重新开辟一段堆内存将源对象堆区的内容赋值到新堆区中
浅拷贝带来的问题:
class A{
public:
A(int i):m_ptr(new int(i)){}
int*m_ptr;
~A(){if(m_ptr!=nullptr) delete m_ptr;
m_ptr=nullptr;}
};
int main()
{
A a1;
A a2=a1;
}
//运行出错:因为a1中的m_ptr指向的堆区内存被析构了两次
所以类中如果有指针那么一般就要深拷贝
加一个复制构造函数
A(const A&other):m_ptr(new int(*other.m_ptr)
{}
再考虑一种情况:当拷贝构造的参数是一个右值得时候如果再去开辟一段堆内存,由于右值马上就会析构它的那块堆内存没用了,
所以为了提高性能,我们可以新对象的堆内存直接指向右值对象的堆内存,然后将右值对象的堆内存指针置空
所以对于存在堆内存的类一般还需要提供一个右值引用构造函数
A (const A&&rvalue)
{
m_ptr=rvalue.m_ptr;
rvalue.m_ptr=nullptr;
}
所以对于需要大量得移动堆区内存的类我们需要设计右值引用的赋值和构造函数
std::move只能对于类或者文件句柄等,但是对于一些基本类型比如int char*…还是会发生深拷贝,因为他们没有对应的右值引用拷贝构造函数