6

std::move()实际应用分析

 3 years ago
source link: https://www.lanindex.com/stdmove%e5%ae%9e%e9%99%85%e5%ba%94%e7%94%a8%e5%88%86%e6%9e%90/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

std::move()实际应用分析

2017/10/18 · Leave a comment

最近因为项目原因接触到了C++11的移动语义(move-semantics),参考了大量资料。不少资料都是从C++左右值的概念到C++11的右值引用,再牵扯出构造函数的实现方式,最后给出一个“实际”意义不高的一串代码,到最后也看不出这个新加入的语义是为了解决什么问题。

于是想自己记录一篇文章,用几句话来解释一些基本概念,几段代码来展示它我个人认为最直观的用途。

左值(lvalue)与右值(rvalue)

用一句话来概括形容就是:

左值:放在等号左边的值,可以被赋值;

右值:放在等号右边的值,不能被赋值;

比较典型的左值(以下各种运算符均是C++内建):

a->m

比较典型的右值(以下各种运算符均是C++内建):

a < b

深拷贝与浅拷贝

比如我们有A、B两个对象,里面均有指针指向对应内容,现在A要对B进行拷贝:

深拷贝:A先申请出一片新的空间,完全复制B的内容到新空间中;

浅拷贝:A复制B指针,将自己的指针指向B的内容地址,A、B公用一块内存地址;

所以对于深拷贝,A的修改与B没有关系;对于浅拷贝,A的修改也会在B对象也会被改变。

左值引用与右值引用

左值引用:传统的引用,形如T&;

右值引用:C++11新的数据类型,为了实现移动语义与完美转发所需要而设计出来的新的数据类型,形如T&&;

std::move()使用的意义

协助使用者进行浅拷贝,前提条件是拷贝对象需要支持移动赋值(move-assignment)、移动构造(move-constructor)。

一个具体的例子,在没有使用std::move()之前:

//test.cpp
#include   <map>
#include   <stdio.h>
int main()
{
std::map<int, int> mapA;
mapA[1] = 1;
mapA[2] = 2;
mapA[3] = 3;
printf("addr A: %x|%x|%x\n", &mapA[1], &mapA[2], &mapA[3]);
std::map<int, int> mapB = mapA;
printf("addr B: %x|%x|%x\n", &mapB[1], &mapB[2], &mapB[3]);
mapB[4] = 4;
printf("addr new: %d|%d\n", mapA[4], mapB[4]);
return 0;
}
$g++ -std=c++11 -o test test.cpp
$./test
addr A: 1646034|1646064|1646094
addr B: 1646124|16460c4|16460f4
addr new: 0|4

在使用了std::move()之后:

//test.cpp
#include   <map>
#include   <stdio.h>
int main()
{
std::map<int, int> mapA;
mapA[1] = 1;
mapA[2] = 2;
mapA[3] = 3;
printf("addr A: %x|%x|%x\n", &mapA[1], &mapA[2], &mapA[3]);
std::map<int, int> mapB = std::move(mapA);
printf("addr B: %x|%x|%x\n", &mapB[1], &mapB[2], &mapB[3]);
mapB[4] = 4;
printf("addr new: %d|%d\n", mapA[4], mapB[4]);
return 0;
}
$g++ -std=c++11 -o test test.cpp
$./test
addr A: 2017034|2017064|2017094
addr B: 2017034|2017064|2017094
addr new: 0|4

看到在使用std::move之后,B对A进行了浅拷贝,仅仅只赋值了key=1,2,3的指针。那么这里引发了一个新的问题:在move(mapA)之后,我们并不希望再进一步对A中key=1,2,3的对象做操作,否则会引起“不可预期”的结果,比如释放了同一个地址。所以我们需要约束对于move后的对象,应当马上“弃用”(std::map已经自动做了这个操作,在move之后A里面的key全部被清空了)。

这里可能会有人问,为什么要搞这么麻烦,我直接用C++传统的左值引用不就可以了吗?代码如下

//test.cpp
#include   <map>
#include   <stdio.h>
int main()
{
std::map<int, int> mapA;
mapA[1] = 1;
mapA[2] = 2;
mapA[3] = 3;
printf("addr A: %x|%x|%x\n", &mapA[1], &mapA[2], &mapA[3]);
std::map<int, int> &mapB = mapA;
printf("addr B: %x|%x|%x\n", &mapB[1], &mapB[2], &mapB[3]);
mapB[4] = 4;
printf("addr new: %d|%d\n", mapA[4], mapB[4]);
return 0;
}
$g++ -std=c++11 -o test test.cpp
$./test
addr A: 25d7034|25d7064|25d7094
addr B: 25d7034|25d7064|25d7094
addr new: 4|4

通过输出结果可以很明显的看出,传统的左值引用导致A,B互相影响,就语境来说不太利于把A拷贝到B,然后丢弃A的场景。

所以说移动语义补充了这块空白,其核心就是利用浅拷贝来模拟对象的移动行为,提高了效率,让语义更加明确。

(全文结束)

转载文章请注明出处:漫漫路 - lanindex.com

Leave a Comment Cancel reply

Your email address will not be published.

在此浏览器中保存我的名字、电邮和网站。

2 Trackbacks


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK