介绍
C++中有一个重要的优化,就是拷贝省略(copy elision)。
他的定义是:对于值传递的语意表达,会省略去拷贝步骤。
比如如下面的代码:
struct Data {
char* bucket;
int bucketSize;
Data(int size) {
bucketSize = size;
bucket = new char[bucketSize];
}
Data(const Data& d) {
bucketSize = d.bucketSize;
bucket = new char[bucketSize]
memcpy(bucket, d.bucket, bucketSize);
}
};
Data GetData() {
Data d(10);
// other process
return d;
}
void main() {
Data d = GetData();
}
按照传统意义上的流程,会有如下的步骤:
- 在函数GetData中,有一个局部变量
- 函数退出堆栈,会构造一个临时变量(拷贝构造或者是move构造)
- GetData中的局部变量析构
- 用临时变量调用Data的拷贝构造函数来初始化main函数中的局部变量
- 临时变量析构
仅仅是一个简单的赋制操作,就会涉及到这么多的步骤,并且对CPU有很大的负载。总共做了3次构造,两次拷贝。而copy elision所做的就是减少这些步骤,减少拷贝。
那么在加上了copy elision之后,会省去哪些步骤呢?
- GetData()函数的“局部”变量的构造
- 析构
只有这两步。省去了copy构造的步骤,并且没有生成临时变量。这里的局部加上了一个引号,意思是并不是真的临时变量。他可能的实现是这个样子的。
void GetData(Data *) {
// manipulate Data ptr
}
void main() {
Data d;
GetData(&d);
}
NRVO
以上的优化,就是所谓的copy elision中的一个variant---NRVO(named-return value optimization)
RVO
还有一种优化就是RVO(unnamed-return value optimization)
类似的这样的
Data unmanedGetData() {
return Data();
}
函数中直接返回一个匿名变量也是这种优化。直接对目的变量做优化,不需要临时变量,不需要拷贝。
注意事项
这种优化只是c++ standard的一种建议(c++17之前,c++17做了强制要求),所以不能想当然的任务所有的编译器都做了这种优化。在使用这些优化之前,应该看一下编译器是否真的做了这些优化。
后记
我在网上看到了一些意见,说这种编译器“自作主张”的优化,是对程序员不友好的。我所以为,持这种想法的人是因为,编译器做了这种优化,就不会调用诸如构造函数这类函数了,那么这些函数所造成的side effect就不会生效。
那么编译器做这种优化好不好呢。我认为是好的。本来像copy constructor这类的函数,从语意(semantic)而言就是一个纯粹的拷贝构造。在这些函数中间插入别的代码,产生了副作用(side effect)就造成了函数本身的不纯粹。这对于函数本身的理念是不相符的。所以我认为,编译器的这种优化是很好的。