wordpress网站数量管理插件,成都网站建设 公司,网站建设明确细节,移动建站价格文章目录 一、概念1.1 左值1.2 左值引用1.3 什么是右值#xff1f;1.4 什么是右值引用#xff1f;对于参数左值还是右值的不同#xff0c;是被重载支持的左值引用的使用场景 和 缺陷 二、移动语义2.1 移动拷贝构造2.2 移动赋值 三、右值引用 与 STL3.1 移动拷贝构造 和 赋值… 文章目录 一、概念1.1 左值1.2 左值引用1.3 什么是右值1.4 什么是右值引用对于参数左值还是右值的不同是被重载支持的左值引用的使用场景 和 缺陷 二、移动语义2.1 移动拷贝构造2.2 移动赋值 三、右值引用 与 STL3.1 移动拷贝构造 和 赋值重载3.2 插入接口3.3 完美转发、万能引用完美转发万能引用 传统的 C 语法中就有引用的语法而 C11 中新增了的右值引用语法特性所以我们管之前的引用叫做左值引用。无论左值引用还是右值引用都是给对象取别名。 一、概念
1.1 左值 左值是一个表示数据的表达式(如变量名或解引用的指针)通俗的理解就是能取到地址的就是左值 正常情况下我们可以对左值赋值定义 const 修饰后的左值不能给它赋值但是可以取出它的地址 左值可以出现在赋值符号 的左边也可以出现在赋值符号的右边 左值具有持久的状态。
// 常见的左值
int a 0;
int b 1;
int* p a;
const int c 3;1.2 左值引用
左值引用就是对左值的引用相当于给左值取别名。左值引用只能引用左值不能直接引用右值但是 const 左值引用可以引用右值。左值引用符号
// 左值引用给左值取别名
int ref1 a;// 左值引用给右值取别名
// int ref2 (a b); // err...(ab)返回的是临时对象临时对象具有常性出现权限放大问题
const int ref2 (a b); // 加 const 就行了1.3 什么是右值 右值也是一个数据表达式右值是 字面常量 或者是 求值过程种创建的临时对象 对于右值不能取出地址不能对它赋值 右值的生命周期是短暂的如字面常量表达式返回值函数返回值不是左值引用的返回值临时变量匿名对象…
// 右值
a b;
func(x,y);
10;
abcd;1.4 什么是右值引用
右值引用相当于给右值起别名右值引用只能右值除非左值 move 后可以对其进行右值引用右值是没有地址的但是右值引用后这个右值引用会被存到特定的位置且可以取到该值的地址也就是说 右值引用值是一个左值。右值引用会开辟一块空间去存右值普通的右值引用可以修改这块空间const 的右值引用则不可以被修改。右值引用符号
move()标准库中的函数可以将左值强制转换为右值
// 右值引用给右值取别名
int ref3 (a b); // 右值引用不能给左值起别名
//int ref4 a; // err// 右值引用可以 给 move 后的左值取别名
int ref4 move(a);对于参数左值还是右值的不同是被重载支持的
//void func(const int a) // 这样虽说都可以使用但是区分不了左值和右值
void func(int a)
{cout void func(int a) endl;
}void func(int a)
{cout void func(int a) endl;
}int main()
{int a 0;int b 1;func(a);func(a b);return 0;
}
----------------------------------
输出结果
void func(int a)
void func(int a)左值引用的使用场景 和 缺陷
左值引用可以直接减少拷贝。应用如下
左值引用传参传引用返回。
左值引用解决了大多数场景的问题但也存在一些解决不了的问题:
局部对象返回问题对象深拷贝问题。
C11 前对于带指针的容器比如 string会进行深拷贝深拷贝的目的是让赋值和被赋值的对象都正常保存并使用数据互不影响代价比较高。而如果被拷贝对象是右值右值本不需要留存浅拷贝就可以但还是进行了深拷贝是很不划算的。
ttang::string s1(hello world);ttang::string ret1 s1; // 左值拷贝
ttang::string ret2 (s1!); // 右值拷贝,如果也是深拷贝很不划算
// 实际上右值拷贝C11使用的是移动拷贝构造是浅拷贝
// 可以很大程度的优化右值的拷贝效率二、移动语义
2.1 移动拷贝构造 移动拷贝构造函数跟构造函数一样参数需要是一个本身类型的对象但 移动拷贝构造函数的参数是一个该类型的右值引用。 所谓移动是数据交换。移动拷贝函数创建出一个新对象将新对象中的值都设置为 0接下来与传进来的右值对象进行资源交换。
// 移动拷贝构造
string(string s):_str(nullptr),_size(0),_capacity(0)
{swap(s);
}根据函数匹配规则如果调用拷贝构造对象的时候传的是左值编译器会自动匹配到拷贝构造函数如果传的是右值那么就会匹配到移动拷贝函数。 使用移动拷贝构造函数后源对象指向资源就被交换出去这些资源的所有权都归属到了新对象。 因此如果源对象是一个长期存在的对象的时候需要谨慎使用移动拷贝构造函数。调用移动拷贝构造函数创建出 s3s1 的资源被转移到了 s3s1 中没有指向任何资源所以就不能通过 s1 去寻找之前的资源。 如果只是调用 move() 函数而不对其返回值进行接收是不会改变传入值的内容的。
std::string s1(hello);// 拷贝
std::string s2 s1;
// 移动s3 里面存了“hello”而 s1 空了
std::string s3 move(s1);// 只是这样不接受其返回值是不会改变s2的
move(s2);
std::string s4 s2;一些使用举例
listttang::string lt;ttang::string s1(hello world);
lt.push_back(s1); // 深拷贝
lt.push_back(move(s1)); // 移动lt.push_back(ttang::string(hello world)); // 移动匿名对象也是右值
lt.push_back(hello world); // 移动总的看来
左值引用减少拷贝右值引用也是减少拷贝提高效率但是他们的角度不同左值引用是直接减少拷贝右值引用是间接减少拷贝通过函数重载识别出是左值还是右值如果是右值则不再深拷贝直接移动拷贝提高效率。 2.2 移动赋值
对于初始化
如果 string 类中只有一个移动拷贝构造函数那么函数返回值构造临新对象的时候那么只需要调用一次 移动拷贝构造 函数将资源转移给新对象。
对于已经初始化过的对象进行赋值
那么就会调用一次 移动拷贝构造 函数和一次 赋值重载 函数赋值重载函数也是进行深拷贝的。
因此为了解决赋值重载的深拷贝的问题我们还需要再实现一个移动赋值重载函数移动赋值重载 函数跟拷贝构造函数一样都是 解决深拷贝的问题都是进行转移资源。
移动赋值重载函数跟赋值重载函数的定义是类似的只是移动赋值重载函数的参数是右值引用是为了让右值能够调用该函数如下
// 移动赋值
string operator(string s)
{swap(s);return *this;
}三、右值引用 与 STL
综上推演C11 为很多容器都增加了 移动拷贝构造函数 和 赋值重载函数的右值引用版本包括 push_back 或者 insert 接口也增加了右值引用 的重载。
3.1 移动拷贝构造 和 赋值重载 3.2 插入接口 上面的文档中insert 和 push_back 可以接收 const 左值引用也就是说这个函数既可以接收左值也可以接受右值那么为什么还需要多定义一个参数是右值引用的函数重载呢
因为我们说了左值引用可以接收右值但是相应的拷贝任然是深拷贝重载右值引用版本正是为了优化这一点。再梳理一下传入右值的流程如果调用 vector 中 insert 时传的参数是右值那么就会编译器就会匹配到右值引用参数的 insert 的重载函数因为 insert 函数内部会对该参数值进行赋值重载到 vector 内如果是右值那么就会调用移动拷贝构造函数所以可以避免深拷贝的出现。
vectorstring v;
string str(hello);
v.insert(v.end(), str); // str 是左值深拷贝
v.insert(v.end(), string(hello~)) // string(xx) 是匿名对象调用右值版本因此我们在赋值、构造、以及调用 insert、push_back、时如果涉及深拷贝的问题尽量传右值匿名对象这样可以减少深拷贝的问题。
3.3 完美转发、万能引用
完美转发
关于右值引用本身的属性举例
int a 10;
cout a endl; // a 可取地址
a; // a 可修改右值引用的 a虽然是通过右值得到的但 a 本身是左值进一步举例
void insert(iterator pos, const T val)
{//...*pos val; // val 是左值调用的是赋值重载_end;
}很明显的在上面调用参数是右值引用的 insert 接口中存在一个问题如果右值引用 val 去接受一个右值那么这个 val 就会退化成一个左值。所以 *pos val; 这一步调用的还是重载函数不是移动拷贝构造。因此为了保持 val 是一个右值有一个专门的写法
std::forwardT(val)它可以在传参的时候保持 val 原生属性也就是可以保持其右值属性因此这样可以保证 *pos val; 调用的会是移动赋值重载函数。
这种调用 std::forward(val)使得 val 保持原生属性的过程就是 完美转发写成如下
void insert(iterator pos, const T val)
{//...*pos std::forwardT(val); // 保持了 val 的右值属性调用的就是其移动赋值函数_end;
}万能引用 万能引用 又叫 引用折叠使用 进行定义或申明。万能引用定义的参数即可以对左值引用也可以对右值引用。 使用场景函数模板的形参 和 auto 声明
templatetypename T
void f(T param); //param是个万能引用auto var2 var1;以上两种场景的共同之处在于它们都涉及类型推导。
下面这两种只是普通的右值引用
templatetypename T
void f(std::vectorT param); // param 是右值引用templatetypename T
void f(const T param); // 加 const 则是右值引用对于万能引用和右值引用的区分不必过多纠结~ 能用就行。
void Fun(int x) { cout 左值引用 endl; }
void Fun(const int x) { cout const 左值引用 endl; }void Fun(int x) { cout 右值引用 endl; }
void Fun(const int x) { cout const 右值引用 endl; }templatetypename T
void PerfectForward(T t) // 这里面用这个右值转化的左值 t 可以但是再转一层就会出问题
{Fun(forwardT(t)); // 不加完美转发的话会全是左值引用
}int main()
{int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值PerfectForward(10); // 右值const int b 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}如果本文对你有些帮助请给个赞或收藏你的支持是对作者大大莫大的鼓励(✿◡‿◡) 欢迎评论留言~~