移动语义
移动语义的使用场景
在写代码的时候会出现低效拷贝。如在string s1 = get_str();
中先构造了一个临时对象,然后将这个临时对象拷贝赋值给了s1。
最高效的操作应该是s1直接表示get_str
中构造的s内存。
移动语义的实现就是为了减少拷贝。
string get_str(){
string s;
...
return s;
}
int main(){
string s1 = get_str();
}
移动语义的使用
实现移动语义,就是实现类的移动赋值函数和移动拷贝函数。
class Array {
Array(int sz);
Array(const Array& other);//拷贝构造函数
Array& operator = (const Array & other); //拷贝赋值函数
Array(const Array&& other);//移动构造函数
Array& operator = (const Array && other);//移动赋值函数
~Array(); //析构函数
private:
size_t sz; //记录资源个数
int* m_data; //指向资源的指针
};
在语法上,拷贝函数参数是左值引用,移动函数参数是右值引用
在实现上,拷贝函数被拷贝的原对象不会有改变,自身的值变为拷贝对象的值;移动函数不保证原对象的值会变成什么样,只保证原对象的状态是安全的,可析构的,原对象的值“移动”给自身用。
class Array {
...
//拷贝构造函数
Array(const Array& other) {
sz = other.sz;
m_data = new int[sz];
for (size_t i = 0; i < sz; i++)
...//拷贝数据
}
//拷贝赋值函数
Array& operator = (const Array& other){
sz = other.sz;
delete[] m_data;
m_data = new int[sz];
for (size_t i = 0; i < sz; i++)
...//拷贝数据
}
//移动构造函数
Array(const Array&& other) {
sz = other.sz;
m_data = other.m_data;
other.m_data = nullptr;
other.sz = 0;
}
//移动赋值函数
Array& operator = (const Array&& other) {
sz = other.sz;
m_data = other.m_data;
other.m_data = nullptr;
other.sz = 0;
}
...
};
在使用上,左值引用只能绑定左值,右值引用只能绑定右值。(const左值引用特殊,可以绑定右值)。
因此要声明一个右值引用应该使用move。
move底层实现是使用static_cast完成了一个类型转换,并没有实际的移动的操作。
int main() {
Array a1(10);
Array& lref = a1;
Array a3(a1); //拷贝初始化
Array a4(lref);//拷贝初始化
Array&& a4 = a1;//错误,右值引用不能绑定左值
Array&& a5 = move(a1);
Array a4(a5);//移动初始化
PS:右值和左值的区别
左值:有一定的生命周期
右值:生命期一般不超过一句话,辅助表达式的执行(如临时对象)
所以右值引用是左值。
完美转发
完美转发的使用场景
在一些函数中,一些参数是需要转发给另外的函数的
比如标准库revoke(f,args...)
执行f(args...)
比如vector的emplace_back(args...)
,会在vector的尾部利用args来构造一个类,需要调用构造函数。
完美转发就是希望在传参时,传入的类型不变。如果args是左值引用的,那么传给构造函数的也应该是左值引用;如果args是右值引用的,那么传给构造函数的也应该是右值引用。
完美转发的使用
参数传入要写成万能引用,转发时调用forward
。
template<typename Func,typename T>
auto myInvoke(Func f,T&& arg){
return f(std::forward<T>(arg));
}
T&& arg
是万能引用的写法,详见这个博客。
必看:
大佬视频,深入又清晰