对C++11中的`移动语义`与`右值引用`的介绍与讨论 (7)

上面进行到b(a)这一步的时候, 其实a已经丢失了对Triangle对象的所有权, 其所有权转交给了b. 之后, a其实已经不持有任何对象了. 再往后a->area()显然就是做无米之炊.

当然, auto_ptr虽然来说比较危险, 但也有它自己适合的应用场合. 工厂函数就是一个特别适合auto_ptr发光发热的地方, 如下:

auto_ptr<Shape> make_triangle() { return auto_ptr<Shape>(new Triangle); } auto_ptr<Shape> c(make_triangle()); // c指向的是一个工厂生产的全新的Triangle对象 double area = make_triangle()->area(); // area是另外一个全新Triangle对象算出来的面积

上面就是一个安全的例子: 确实有两个Triangle对象. 其实这个安全的例子, 和上面那个完犊子的例子, 其实都有同样的代码书写方式:

auto_ptr<Shape> var(狗); double area = 狗->area();

你心里明白, 同样的写法, 完犊子的例子之所以完犊子, 是因为狗把自己的资源在第一步交给了var, 自己一无所有了. 而工厂例子中, 每次狗都持有一个全新的资源.

但从另外一个角度来看问题: 两个例子中, a和make_triangle()这两个表达式还有什么其它区别吗? 表面上看, 它们都是同一类型的表达式(auto_ptr<Shape>), 那为什么二者表现不一致呢? 这是因为二者的值类别(value categories)不同: a是一个左值(lvalue), make_triangle()是一个右值(rvalue).

值类别, value categories

我们依然从C++98与C++03的标准来一步一步的看这个问题. 在C++98与C++03中, 值类别是如此的不言自明(注意, 当我们讨论值类别的时候, 讨论的是表达式, 而不是变量), 以至于很长一段时间里, 大家都不去关心这个事情, 值类别只有两种选择: 左值(lvalue)和右值(rvalue). 所谓左值, 就是可以出现在赋值操作符左侧的表达式. 所谓右值, 是指仅可以出现在赋值操作符右侧的表达式.

上面的例子中, 表达式a是一个auto_ptr<Shape>类型的变量, 这显然是一个左值, 因为a是一个变量, 可以被赋值. 而make_triangle()这个表达式是一个函数调用表达式, 其值是为其返回的对象(按值返回的对象), 每次调用它都会在内部创建一个新的auto_ptr<Shape>然后通过值返回的方式返回回来. 这显然是一个右值表达式.

我们从值类型的角度来看auto_ptr中的移动语义, 结合上面提到的"完犊子"与"工厂"两个鲜活的例子, 可以得出一个例子:

移动左值是一种危险的行为. 因为移动代表着剥夺, 但左值可能在内部资源被剥夺之后, 错误的再去尝试使用内部资源

移动诸如make_triangle()这样的右值则是一种安全的行为. 因为这种右值本身就是一次性的, 阅后即焚的, 即便你不剥夺它的内部资源, 它也会在下个;后被析构.

或者形象一点, 左值就像是一个正常的人, 能活到90岁(所属作用域终止), 你不能随便就把一个正常人的肾挖掉. 但右值就像是一个被判决死刑立即执行的人一样, 我们可以心安理得的将死刑犯的肾挖掉. 反正下午就嗝屁了, 不如死前给和谐社会做一点贡献.

auto_ptr<Shape> c(make_triangle()); ^ make_triangle()表达式的值所指向的Triangle对象, 活不过这个分号

其实左值与右值这个概念是从C语言一脉相承继承过来的. 左值可以出现在=左边, 右值只能出现在=右边这句话在C语言范畴中, 是绝对正确的. 但在C++98或C++03中, 并不完全正确, 这里举几个反例:

数组变量, 或删除了=操作符的类变量, 是没法出现在=左边的, 但它们都是货真价实的左值

如果一个类, 实现了=操作符, 但它的语义并不是赋值的话, 这种类的变量也有可能出现了=的左边(这确实有点抬杠了, 但你不能说这不是一个反例)

在C++98与C++03中, 或许我们这样定义左值和右值可能会更精确一点:

无论左值还是右值, 本质都是值, 都是对象, 都在内存中占用一块区域

左值是有名字的值, 像变量就是典型的左值(变量名, 或者引用名就是名字), 意味着这块内存区域在它的作用域范围内, 可以通过名字被多次访问. 它的生命周期一般与作用域一致

右值则是没有名字的值, 一般仅在表达式求值的那一个时刻可以访问这块内存区域, 之后就没有办法再去访问这块内存了.

注意, 我们当前的讨论, 还没有超出C++03的范畴.

 右值引用

从auto_ptr的例子我们可以看出移动语义本身的性能潜力, 但也看到了潜在的安全风险. 那么, 有没有一种机制, 能自动判断表达式的值类别, 如果是左值, 就对其执行拷贝, 如果是右值, 就对其执行移动呢?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzspdp.html