浅谈C++11标准中的复制省略(copy elision,也叫RVO返回值优化)

严正声明:本文系作者davidhopper原创,未经许可,不得转载。

C++11以及之后的C++14、17标准均提出一项编译优化技术:复制省略(copy elision,也称复制消除),另外还有RVO(return value optimization,返回值优化)或NRVO(named return value optimization,具名返回值优化)的提法,其实都是一回事。维基百科的Copy elision词条以及cppreference中的Copy elision词条均对该技术做了描述,但写得过于学术化,让人似懂非懂。RVO V.S. std::move写得不错,可以学习借鉴。这项优化技术的中心思想是:一个函数若直接返回类对象,一般会生成临时类对象变量,需多次调用拷贝构造函数(copy constructor)造成效率低下,编译器对此优化,省略其中的拷贝构造环节,达到提升效率的目的。

一、示例

下面来看一个具体示例。首先定义一个BigObject类:

class BigObject {
public:
  BigObject() : person_names_(std::vector<std::string>(1000000)) {
    std::cout << "constructor. " << person_names_.size() << std::endl;
  }
  ~BigObject() {
    std::cout << "destructor. " << std::endl;
   }
  BigObject(const BigObject& other) {
    person_names_ = other.person_names_;
    std::cout << "copy constructor. " << person_names_.size() << std::endl;
   }

private:
  std::vector<std::string> person_names_;
};

其次,按照C++菜鸟的思路,定义一个直接返回BigObject类对象的函数:

// 不管C++标准如何规定,强烈反对写这种没有一点C++味道的垃圾代码!!!
BigObject Foo() {
 BigObject local_obj;
 return local_obj;
}

最后,在main函数中调用Foo函数:

int main() {
  BigObject obj = Foo();
  return 0;
}

将上述代码全部放置于一个叫test.cpp的文件,内容如下:

#include <iostream>
#include <string>
#include <vector>

class BigObject {
public:
  BigObject() : person_names_(std::vector<std::string>(1000000)) {
    std::cout << "constructor. " << person_names_.size() << std::endl;
  }
  ~BigObject() {
    std::cout << "destructor. " << std::endl;
   }
  BigObject(const BigObject& other) {
    person_names_ = other.person_names_;
    std::cout << "copy constructor. " << person_names_.size() << std::endl;
   }

private:
  std::vector<std::string> person_names_;
};
// 不管C++标准如何规定,强烈反对写这种没有一点C++味道的垃圾代码!!!
BigObject Foo() {
 BigObject local_obj;
 return local_obj;
}

int main() {
  BigObject obj = Foo();
  return 0;
}

GCC编译器默认打开“复制省略”优化选项,因此我们使用下述编译指令手动禁用该选项:

g++ -g -fno-elide-constructors -Wall -std=c++11 test.cpp -o test

执行生成的文件:

./test

结果如下:

constructor. 1000000
copy constructor. 1000000
destructor. 
copy constructor. 1000000
destructor.
destructor. 

可见,未开启“复制省略”优化选项时,直接返回BigObject类对象,会造成额外调用两次拷贝构造函数。在我的Ubuntu 16.04系统上,采用gcc 5.4.0编译器,一个空std::string类对象占用32字节的空间,调用一次BigObject类的拷贝构造函数约消耗:(1000000 * 32) / (1024 * 1024) = 30.52 (MB)内存。若该函数在一个10000次的循环中调用,光是这种无谓的内存分配开销就将达到:10000 * 2 * 30.52 / 1024 = 596.1 (GB) 。不是说会一次占用这么多的内存,而是说内存空间会无谓地进行596.1GB的内存分配和释放操作,这是多大的空间浪费!又会造成多大的效率折扣!

如果使用GCC编译器默认提供的“复制省略”优化选项:

g++ -g -Wall -std=c++11 test.cpp -o test

执行生成的文件:

./test

结果如下:

constructor. 1000000
destructor. 

彻底消除了拷贝构造函数的调用,执行效率大为提升!

二、结论

尽管C++11以上标准提出“复制省略(copy elision)”优化策略,并且GCC等主流编译器均支持该优化,但我强烈不建议使用该技术。这样写出来的代码还有C++的味道吗?如果C++可以直接返回类对象,那还要C++干什么?直接使用Python或者Matlab就行了。我们在使用C++语言时,必须牢记,返回值必须是intbool、枚举或指针等轻量级原生(lightweight primitive)数据类型。如果确实需要返回大型数据,请使用引用或指针作为输出参数返回,而不是通过return语句返回。

  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值