有几个地方是需要跟同学们
特别解释的
一个地方就是
如果我真地只需要实现
刚才那个例子里边的那个移动语义
其实左值引用 它本来就可以完成
为啥呀 引用本身就意味着
可以修改目标数据对象的值
你移动语义的目的
不就是把那个东西拷贝过来
同时把那个指针（那个所有权）
给我转交过来吗 对吧
我之所以在左值引用参数列表里面
前面加一个constA&
变成对一个constA的一个引用（左值引用）
目的无非是表明
我这个函数内部不想修改它
如果你想修改它
你把const去掉不就完了吗
它不就能修改吗
而我们移动赋值
移动拷贝要的不就是修改吗
你想达到这个修改的目的
const去掉就可以做到
如果你想同时提供这两种语义
如果你同时想提供移动语义
和拷贝语义的话
那么事实上你就重载两个版本就行了
一个是const版本
一个是非const版本 就完了
一样是可以做到这一点的
如果它是一个const
那我们就调用const版本
如果它不是const
我们就调用它的非const的版本
如果它是非const
我们又想调用它的const版本
把它转型成const
然后再调用就完事了 对吧
非const版本移动语义
const的版本拷贝语义 没问题
对于我们程序来讲这是两个不同的函数
有没有const
它的函数签名是不一样的
所以是两个不同的函数
重载是完全合法的
可以这么做 没有问题
你看我们的实现 这是一个移动版本
所以我们要修改目标数据对象的值
看看_n被改了
_p被改了 对吧
这是一个const的版本
所以我们深拷贝 赋值操作符也一样
这是一个移动赋值动作
所以要修改目标引用的内容
这个呢 是一个深拷贝的动作
所以跟刚才那个实现一样
要new的 很简单哪
我们左值语义就能够搞定它
左值引用就够了 要右值引用干嘛呢
就是为了实现这个移动语义
弄了一个很奇怪的模式去来实现它
事情当然不是那么简单
看看我们的主程序
我们刚才那个例子怎么用
首先 我定义一个量a1
我直接写的Aa1
因为后面没有任何参数传进去
所以它是一个缺省构造
const Aa2 它依然是一个缺省构造
但是它是一个常量 不是变量
这是不同的地方
所以我缺省构造出一个常量来
然后再也不变了
我们这里面只是一个示例
这事很奇怪 很少见 对吧
但是它本身是合法的
然后我用a1去构造a3
这是一个拷贝构造
传的是一个A类的对象
所以它会调用A::A(A&)
也就是我们的移动构造语义
传的是A& 所以它会移动构造
意味着你可以修改目标引用的值
所以它会调用移动构造那个版本
a2呢 是个constA&传进来的
你传的是constA的这个对象嘛
所以引用传递 传的就是const A &
那么它就会调用A::A( const A & )这个函数
这个构造函数是什么呢
就是我们实现的深拷贝语义
所以它就会深拷贝
第一个移动构造 第二个拷贝构造
看 区分开了吧
那么你可能就会说
我想构造这个对象a5
我以a1这个对象来构造a5
但是我想进行移动构造
而不是拷贝构造
如果你写a5(a1) 那就是移动构造
它不是拷贝构造
如果我想拷贝构造 怎么办呢
就有一个招 const_cast( a1 )
跟那个static_cast一样
它也是一个类型转换的关键字
具体的解释我们也要
在下一节里边去讨论
现在我们就需要知道
如果这样一个量 它不是const
我们可以通过const_cast这样一个转换
临时把它转换成一个const
它原来是变量 我用const_cast这个转换
就能够临时把它转换成一个常量
如果它原来是一个常量
我就可以通过const_cast转换
临时把它转换成一个非常量
就是取消或设置这个量的const属性
临时地做一下 我们这个目的
就是为了把a1转换成const A 而不是A
然后调用它那个拷贝构造版本
因为你如果传a1 它就是移动构造
你只有传const A类的对象
它才能够进行拷贝构造
你又想拷贝构造 它本身又是变量
所以只能临时给它加上const
表示它是常量
所以现在我们要拷贝构造
你看我们有右值引用的时候
我们要做什么事情 static_cast
现在呢 我们要做const_cast
有差别吗 没差别
那边要折腾一下 这边也要折腾一下
从劳动强度来讲 基本上差不离 对吧
它能做到这一条
再看下面的代码
我又定义了三个量：a6、a7、a8
把a6赋值为a1，a7赋值为a2
a8赋值为const_cast(a1)
把它转型成常量 然后赋值给a8
跟我们的构造函数的性质是一样的
这是赋值操作符重载 对吧 一样的
你看 没有右值引用 只有左值
这个移动语义也有了 拷贝语义也有了
都在嘛 一样是4个函数啊
跟刚才左值引用、右值引用
混合着用不一样吗 都是四个函数
你说那我现在搞了一个右值引用
来实现移动语义 有意义吗
左值引用就能够搞定的事嘛
那么如果你仅仅这么理解
那么就意味着你事实上
对我们的引用语义 尤其是右值引用语义
就没有了解得很深刻
它的意义、目的并不仅仅体现在这里
虽然最初是想实现拷贝语义
但其实并不仅仅是为了实现拷贝语义
就像我刚才讲的 不用它
一样可以实现拷贝语义
右值引用的第一个意义是
右值引用可以使用文字
作为函数的实际参数
这是一个细微的
但是很有有趣的改变
你比如讲我有一个函数 叫int f(int& x)
这是一个左值引用的版本
所以return++x
它可以把这个结果带回去的
引用嘛 可以把结果带回去的
++x 那个实际对象的值就改了 对吧
它就会变加1了嘛
然后我们返回加1后的值
这个函数本身很简单就一条语句
但是你要特别注意
这个函数 它不接受文字类型的参数
因为什么呢
因为我们获取不了那个文字的左值
我们后面看代码 你就明白了
第二个版本int f(int&&x) 传右值引用
内部仍然是return++x
右值引用 传这个 对吧
这个时候因为我们传的是右值
所以实际上
它接受文字作为实际参数 这是可以的
因为它传递的就是右值引用
在函数内部呢 就是我刚才讲的
有个基本规则
具名的右值引用在函数内部
将会作为左值来看
匿名的右值引用 它将作为右值来看
理论上是这样 但是看代码
如果有一段代码是这么写的
std::cout<< ++10 <<std::endl
那么这个代码编译器是不接受的
通过不了 因为什么呢
10 它是一个整型的文字
这个文字只有右值没有左值
它能赋值给别人
它绝不能够被别人所赋值
所以++10这个语句本身是非法的
编译器是不接受的
没有左值就不能“++”
这是肯定的一点
编译器通不过 明白了吧
我们可以调用这个函数std::cout<< f(10)
10是一个文字 作为函数的参数传进去
我们实际上只能传那个文字的右值引用
而不能传它的左值引用
这是需要特别明确的地方
没有左值啊 引用什么呢 对吧
只能传递它的右值引用
在函数内部
我们只能以右值的方式来对待它
刚才不是说了吗
传一个右值引用进来
在函数内部是当作一个左值引用
来对待的呀
它是有限制条件的
就是我们要尽可能地
把它当作左值引用来对待
是因为我们要修改目标数据对象的值
所以限制条件就是
我前面说的具名右值引用作为左值
而匿名右值引用作为右值
因为10 它是个匿名的右值引用
10是没有变量的名字的——它不是变量
它仅仅是个文字 它是没有名字的
所以它是个匿名的右值对象
匿名的右值数据
那么这样的话 它就是个匿名右值引用
那么在我们的函数内部
f这个函数内部只能作为右值
如果作为右值 ++x
它就是非法的 做不了
只有左值 才能++x 右值做不了
好嘛 编译器会告诉你
这个代码也是错的 理论上是这样
有的编译器会给你输出11
就把那个1加到10上面
然后给你个11
什么编译器会得到这个结果呢
我就不说那个编译器的名字
你回去看一看 多试几个编译器
有的时候就会给你一个
极其奇怪的结果
理论上不应该的 但恰恰就出现了
更重要地
右值引用的最主要的目的其实是这个
主要是避免编写过多的构造与赋值函数
同学们首先要记得
不管是左值引用还是右值引用
如果你想要同时提供
我们的拷贝语义和移动语义
那么你至少需要2对（4个）
构造和赋值函数的
构造函数一个 赋值函数一个 这是一对
必须要成对出现的
你至少需要提供2对
一个移动版本的
一个是拷贝版本的 对吧
还有 如果你这个类里边
有很多指针对象成员
或者其他对象成员
而且这些对象成员涉及到
指针所有权的问题
那么你可能需要或者你可能想为
这些指针成员中的每一个
都提供一个移动语义和拷贝语义
这事情就要命了 你知道吧
一个指针量 我需要4个函数（成员函数）
俩移动语义的 俩拷贝语义的
需要写4个 这是一个指针
俩指针我要写几个呢 我要写8个
因为第一个指针拷贝 第二个指针也拷贝
第一个指针拷贝 第二个指针移动
第一个指针移动 第二个指针拷贝
第一个指针移动 第二个指针也移动
你看 拷贝拷贝、拷贝移动、
移动拷贝、移动移动
组合爆炸 写不过来啊
所以参数如果很多
而且都需要涉及到
移动语义和拷贝语义的混合编程
这里构造函数
写起来能累死你 太费劲了
没有右值语义 这个问题搞不定
光有右值语义也搞不定
必须要会我们下一讲要讲的泛型编程
否则的话你搞不定这个题
最后一个
它实际上用于实现一个完美转发
什么叫完美转发呢
完美转发的意思就是
完成一个参数从一个函数
到另外一个函数的传递 这叫转发
一个函数在运行期间
它会接受一个参数
它内部可能不做处理
也可能只做简单处理
然后把参数传递给另外一个函数
转发对库的设计可能是很重要的
这样的一个机制
在右值引用出现之前
我们就没有一个完美转发过程
不管你使用什么样的逻辑来实现它
这个转发总是有一些缺陷的
我们转发最好能够做到什么程度呢
就是接受什么样的值
就传出去什么样的值
而没有右值引用 这个事是做不到的
右值引用的最主要的目的
第二个目的 就在这里
至于怎么样实现完美转发
我们下一讲讲泛型编程的时候
会再讨论它