1. // style 1
  2. HeavyObject func(Args param);
  3. // style 2
  4. bool func(HeavyObject* ptr, Args param);

看起来style 2虽然使用时需要写两行代码,但函数内部的成本却是确定的。

返回值优化

RVO是Return Value Optimization的缩写,即返回值优化,NRVO就是具名的返回值优化,为RVO的一个变种,此特性从C++11开始支持。

  1. HeavyObject func()
  2. {
  3. return HeavyObject();
  4. }
  5. // call
  6. HeavyObject o = func();

按照以往对C++的理解,HeavyObject类的构造析构顺序应该为,Constructor,Copy Constructor,Destructor,Destructor。

但是实际运行后的输出结果却为,Constructor,Destructor。实际运行中少了一次拷贝构造和析构的开销,编译器帮助我们作了优化。实际上这里就是先创建外部的对象,再将外部对象的地址作为参数传给函数func,类似style 2方式。

NRVO

  1. HeavyObject func()
  2. {
  3. HeavyObject o;
  4. return o;
  5. }
  6. // call
  7. HeavyObject o = func();

返回一个具名的本地对象时,编译器优化操作如第一种使用方式一样直接在外部对象的指针上执行构造函数,只是如果构造失败时还会再调用析构函数。

优化条件

从上述实现方式可以看到,如果你的函数实现功能比较单一,比如只会对一个对象进行操作并返回时,编译器会进行RVO优化;如果函数实现比较复杂,可能会涉及操作多个对象并不确定返回哪个对象时,编译器将不做RVO优化,此时函数返回时会调用类的拷贝构造函数。

  1. HeavyObject func()
  2. {
  3. HeavyObject o;
  4. return static_cast<HeavyObject&>(o);
  5. }
  6. // call
  7. HeavyObject o = func();
  8. // Constructor
  9. // Copy Constructor
  10. // Destructor
  11. // Destructor
  12. HeavyObject func()
  13. {
  14. return std::move(HeavyObject());
  15. }
  16. // call
  17. HeavyObject o = func();
  18. // Constructor
  19. // Move Constructor
  20. // Destructor
  21. // Destructor

上述两种使用方式可以看到,当返回一个对象时且对象类型与返回类型不一致时,编译器将不做RVO。实际上C++标准文档中有如下描述:

类似优化

A a = A(); 不会创建临时对象,已经被优化掉只会调用普通构造函数,概念上是有一个临时对象,但是并不存在。但是int a = A().c;这样写则会有一个临时对象,在这个语句结束时就析构掉了。 const int& a = A().c;为了保证我们使用 a 的时候依然存在,此时临时对象的生命周期会加长到作用域结束。