不要拷贝


C++对值语义的支持,导致程序员一不留神就会多出一次“拷贝”的过程。既然选择了C++这种坑多开发效率又低的语言,一定是因为对于性能有比较高的需求(不追求性能,Python、JS、Java选哪个不好?),所以对于无处不在的拷贝,还是尽量避免为好。

对于避免拷贝,一般有以下两种做法:

引用,而不是拷贝


引用最初在C++中提出是为了能够更“原生”地处理运算符重载的结果,比如

1
vec[1] = 10;

这里重载的运算符[]就会返回一个引用,所以我们可以像一般变量一样对其赋值。

说回利用引用避免拷贝,一般可以检查如下三种情况:

a.临时变量

对于嵌套很深的变量,比如

1
object_a.GetAttrB()->GetAttrC()->GetAttrD()

如果这个变量在之后多次被用到,一般地,我们会选择用一个常量引用作为其“别名”,避免一次拷贝。

1
const TypeD& d = object_a.GetAttrB()->GetAttrC()->GetAttrD();

b.函数参数

函数的形参绑定到实参时,一般会将实参做一次拷贝。这时,对于那些在函数内不会被修改的参数,可以使用常量引用,避免这一次拷贝。

c.map插入

map的插入经常会被写成这样

1
2
3
A a;
a.init();
m[akey] = a;

这样的话就引入了一次拷贝。

但是实际上,map对于[]的重载保证了如果key不存在时,会自动构建一个对象,并返回其引用,所以可以这样避免拷贝:

1
2
A& a = m[akey];
a.init();


移动,而不是拷贝


移动语义是C++11中引入的,表示资源的“窃取”、“转移”,而不是“拷贝”。它和右值引用,std::move结合,可以减少拷贝的发生。

例如:

1
vec[1] = std::move(objectA);

这里依赖如下几个概念,得以实现“移动语义”,而非“拷贝/赋值语义”

a.std::move将左值转换为右值引用类型  
b.右值引用类型在进行重载函数匹配时,将优先匹配参数为右值引用类型的“移动构造函数”或“移动赋值函数”  
c.移动构造/赋值函数用于移动资源(比如对象内部的指针),并保证移动后的源对象可析构,并且不再使用这些资源。  
d.对于自定义类型,移动构造/赋值函数一般需要自己实现;对于标准库类型(如string),则已由标准库实现  

因此,对于一个“后续不再使用的对象objectA”,当我们想要窃取其资源(如将其放入容器)时,可以通过std::move+右值引用+移动构造/赋值函数这一套组合拳,避免资源的无谓拷贝。


推荐阅读:
使用双buffer无锁化
protobuf中set_allocated_xxx排雷
读写锁的性能一定更好吗

转载请注明出处: http://blog.guoyb.com/2018/03/31/dont-copy/

欢迎使用微信扫描下方二维码,关注我的微信公众号TechTalking,技术·生活·思考:
后端技术小黑屋

Comments