一. 浅拷贝与深拷贝struct Node{ int size; int *p; Node() default; Node(int x){ size x; p new int(size); } };1. 浅拷贝按位拷贝对象如果拷贝对象中存在指针那么两指针指向的地址相同但是两指针的地址不一定相同Node shllowCopy(Node v){ this - size v.size; this - p v.p; }2. 深拷贝拷贝对象的所有属性如果拷贝对象中存在指针那么会在堆上重新开辟一片相同大小的空间并让指针指向该空间Node deepCopy(Node v){ this - size v.size; this - p new int[size]; for(int i 0; i size; i){ this - p[i] v.p[i]; } }二. 左值与右值左值(lvalue)表达式结束后仍然存在的持久对象右值(rvalue)表达式结束后就被销毁的临时对象一般来说有变量名的都是右值没有变量名的是左值特殊字符字面量如c,a这种是右值但是字符串字面量如abcd这种由于它存在于静态存储区所以字符串字面量均为左值三. 左值引用与右值引用1. 左值引用()左值引用本质上是给左值变量起了一个别名改变任意一方双方都改变只能绑定在左值上特殊const int x 10为常量左值引用可以绑定右值但是不可更改2. 右值引用()绑定在右值上可以延长临时对象的生命周期并允许更改应该注意的是右值引用类型的变量它本身是个左值比如说int x 10x的类型是右值引用但它本身是个左值如果你要把x再次当成右值传为另一个函数或者赋值给另一个数那么直接传是不行的这个时候就要用到std::move()四. 强转右值std::move()std::move()可以将左值强制转化为右值类型上变成右值引用类型作用是把原对象指向堆内存空间的指针地址转交给新对象然后将原对象的该指针置为nullptr但是std::move()对基本类型如int,double,char,指针无效因为他们的值就存在于栈上不指向任何外部资源比如说int res 1; int ans std::move(res); std::cout res \n;此时你会发现输出的仍是1如果不是的话那么原对象会被“掏空”比如说std::string s abc; std::string t std::move(s); std::cout s \n;输出的就是空注意只有在std::move()用于创建新对象时才触发移动构造五. 纯右值与将亡值1. 纯右值没有身份没有固定内存地址不可被移动用完即销毁的值/表达式特点没有标识符没有名字不能取地址一般用于初始化和计算2.将亡值有身份可被移动的值/表达式特点通常对应一个右值引用在内存中有地址通过std::move()强转后的就是将亡值六. 函数参数传递问这六次函数调用分别各触发了几次拷贝几次移动构造void f1(Node v){ return; } void f2(Node v){ return; } void f3(Node v){ return; } int main(){ Node a; Node b a; Node c; Node d; f1(a);//1次拷贝0次移动构造 f1(b);//1次拷贝0次移动构造 f1(std::move(c));//0次拷贝1次移动构造 f2(a);//0次拷贝0次移动构造 f2(b);//0次拷贝0次移动构造 f3(std::move(c));//0次拷贝0次移动构造 return 0; }七. 引用折叠规则只要有左值引用结果就是左值引用只有当两个都是右值引用时结果才是右值引用原始类型(T)声明的引用类型折叠后结果TTTTTTTT八. 万能引用万能引用的好处在于既能绑定左值又能绑定右值万能引用形式上为T但要成为万能引用必须满足两个条件必须存在类型推导如模板auto等形式只能是T如果用const修饰就会变回普通的右值引用易错区分void func(std::vectorT v){ return; }这不是万能引用因为它被固定为了std::vector的右值引用。它只能接收std::vector的右值不能接收std::vector的左值更不能接收其他的类型如int万能引用的形式只能是T万能引用是贪婪的这里模板推导的“精确匹配”优先级高于普通函数的“隐式转换”也就是说可能会引发不必要的引用转化九.完美转发std::forwardT用处是当一个函数把参数传递为另一个函数时保留参数的原生属性例void target(int x) { return; } templatetypename T void wrapper(T param) { target(param); //报错 } int main() { wrapper(10); }在这个例子中10确实是右值但是在wrapper函数中param是左值无法进行的右值绑定于是会报错为了解决这个问题我们引入了std::forwardT它能根据T推导的结果决定把param转化为左值或者是右值于是就可以templatetypename T void wrapper(T param) { target(std::forwardT(param)); }
C++移动语义
一. 浅拷贝与深拷贝struct Node{ int size; int *p; Node() default; Node(int x){ size x; p new int(size); } };1. 浅拷贝按位拷贝对象如果拷贝对象中存在指针那么两指针指向的地址相同但是两指针的地址不一定相同Node shllowCopy(Node v){ this - size v.size; this - p v.p; }2. 深拷贝拷贝对象的所有属性如果拷贝对象中存在指针那么会在堆上重新开辟一片相同大小的空间并让指针指向该空间Node deepCopy(Node v){ this - size v.size; this - p new int[size]; for(int i 0; i size; i){ this - p[i] v.p[i]; } }二. 左值与右值左值(lvalue)表达式结束后仍然存在的持久对象右值(rvalue)表达式结束后就被销毁的临时对象一般来说有变量名的都是右值没有变量名的是左值特殊字符字面量如c,a这种是右值但是字符串字面量如abcd这种由于它存在于静态存储区所以字符串字面量均为左值三. 左值引用与右值引用1. 左值引用()左值引用本质上是给左值变量起了一个别名改变任意一方双方都改变只能绑定在左值上特殊const int x 10为常量左值引用可以绑定右值但是不可更改2. 右值引用()绑定在右值上可以延长临时对象的生命周期并允许更改应该注意的是右值引用类型的变量它本身是个左值比如说int x 10x的类型是右值引用但它本身是个左值如果你要把x再次当成右值传为另一个函数或者赋值给另一个数那么直接传是不行的这个时候就要用到std::move()四. 强转右值std::move()std::move()可以将左值强制转化为右值类型上变成右值引用类型作用是把原对象指向堆内存空间的指针地址转交给新对象然后将原对象的该指针置为nullptr但是std::move()对基本类型如int,double,char,指针无效因为他们的值就存在于栈上不指向任何外部资源比如说int res 1; int ans std::move(res); std::cout res \n;此时你会发现输出的仍是1如果不是的话那么原对象会被“掏空”比如说std::string s abc; std::string t std::move(s); std::cout s \n;输出的就是空注意只有在std::move()用于创建新对象时才触发移动构造五. 纯右值与将亡值1. 纯右值没有身份没有固定内存地址不可被移动用完即销毁的值/表达式特点没有标识符没有名字不能取地址一般用于初始化和计算2.将亡值有身份可被移动的值/表达式特点通常对应一个右值引用在内存中有地址通过std::move()强转后的就是将亡值六. 函数参数传递问这六次函数调用分别各触发了几次拷贝几次移动构造void f1(Node v){ return; } void f2(Node v){ return; } void f3(Node v){ return; } int main(){ Node a; Node b a; Node c; Node d; f1(a);//1次拷贝0次移动构造 f1(b);//1次拷贝0次移动构造 f1(std::move(c));//0次拷贝1次移动构造 f2(a);//0次拷贝0次移动构造 f2(b);//0次拷贝0次移动构造 f3(std::move(c));//0次拷贝0次移动构造 return 0; }七. 引用折叠规则只要有左值引用结果就是左值引用只有当两个都是右值引用时结果才是右值引用原始类型(T)声明的引用类型折叠后结果TTTTTTTT八. 万能引用万能引用的好处在于既能绑定左值又能绑定右值万能引用形式上为T但要成为万能引用必须满足两个条件必须存在类型推导如模板auto等形式只能是T如果用const修饰就会变回普通的右值引用易错区分void func(std::vectorT v){ return; }这不是万能引用因为它被固定为了std::vector的右值引用。它只能接收std::vector的右值不能接收std::vector的左值更不能接收其他的类型如int万能引用的形式只能是T万能引用是贪婪的这里模板推导的“精确匹配”优先级高于普通函数的“隐式转换”也就是说可能会引发不必要的引用转化九.完美转发std::forwardT用处是当一个函数把参数传递为另一个函数时保留参数的原生属性例void target(int x) { return; } templatetypename T void wrapper(T param) { target(param); //报错 } int main() { wrapper(10); }在这个例子中10确实是右值但是在wrapper函数中param是左值无法进行的右值绑定于是会报错为了解决这个问题我们引入了std::forwardT它能根据T推导的结果决定把param转化为左值或者是右值于是就可以templatetypename T void wrapper(T param) { target(std::forwardT(param)); }