C++对值语义的支持,导致程序员一不留神就会多出一次“拷贝”的过程。既然选择了C++这种坑多开发效率又低的语言,一定是因为对于性能有比较高的需求(不追求性能,Python、JS、Java选哪个不好?),所以对于无处不在的拷贝,还是尽量避免为好。
对于避免拷贝,一般有以下两种做法:
引用,而不是拷贝
引用最初在C++中提出是为了能够更“原生”地处理运算符重载的结果,比如
vec[1] = 10;
这里重载的运算符[]就会返回一个引用,所以我们可以像一般变量一样对其赋值。
说回利用引用避免拷贝,一般可以检查如下三种情况:
a.临时变量
对于嵌套很深的变量,比如
object_a.GetAttrB()->GetAttrC()->GetAttrD()
如果这个变量在之后多次被用到,一般地,我们会选择用一个常量引用作为其“别名”,避免一次拷贝。
const TypeD& d = object_a.GetAttrB()->GetAttrC()->GetAttrD();
b.函数参数
函数的形参绑定到实参时,一般会将实参做一次拷贝。这时,对于那些在函数内不会被修改的参数,可以使用常量引用,避免这一次拷贝。
c.map插入
map的插入经常会被写成这样
A a;
a.init();
m[akey] = a;
这样的话就引入了一次拷贝。
但是实际上,map对于[]的重载保证了如果key不存在时,会自动构建一个对象,并返回其引用,所以可以这样避免拷贝:
A& a = m[akey];
a.init();
移动,而不是拷贝
移动语义是C++11中引入的,表示资源的“窃取”、“转移”,而不是“拷贝”。它和右值引用,std::move结合,可以减少拷贝的发生。
例如:
vec[1] = std::move(objectA);
这里依赖如下几个概念,得以实现“移动语义”,而非“拷贝/赋值语义”
a.std::move将左值转换为右值引用类型
b.右值引用类型在进行重载函数匹配时,将优先匹配参数为右值引用类型的“移动构造函数”或“移动赋值函数”
c.移动构造/赋值函数用于移动资源(比如对象内部的指针),并保证移动后的源对象可析构,并且不再使用这些资源。
d.对于自定义类型,移动构造/赋值函数一般需要自己实现;对于标准库类型(如string),则已由标准库实现
因此,对于一个“后续不再使用的对象objectA”,当我们想要窃取其资源(如将其放入容器)时,可以通过std::move+右值引用+移动构造/赋值函数这一套组合拳,避免资源的无谓拷贝。
推荐阅读:
使用双buffer无锁化
protobuf中set_allocated_xxx排雷
读写锁的性能一定更好吗
转载请注明出处: blog.guoyb.com/2018/03/31/…
