0%

NRV优化

导言

  之前在阅读候捷老师的《深入探索C++对象模型》一书时,对于NRV优化操作这个编译器优化并还是特别的清楚。所以今天准备通过实际测试来深入了解一下这个NRV的编译器优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
class Edge{
public:
Edge() {
std::cout << "Edge construct!" << std::endl;
Point = 0;
}
~Edge() { std::cout << "Edge destruct!" << std::endl; }
Edge(const Edge&_result) {
std::cout << "Copy Construct!" << std::endl;
Point = _result.Point;
}

private:
int Point;
};
Edge get() {
Edge point;
return point;
}
int main() { Edge point = get(); }

  接下来我们将通过这个例子来深入了解NRV优化

不用NVR优化

1
2
3
4
5
6
7
8
// g++ -o demo test.cpp -fno-elide-constructors
// -fno-elide-constructors 表示输出未优化的结果
Edge construct!
Copy Construct!
Edge destruct!
Copy Construct!
Edge destruct!
Edge destruct!

  在上述代码中,在成员函数get()函数中定义了一个Edge对象,然后将其作为返回值返回。如果按照调用copy construct,编译器会对代码进行一个扩张操作

  • 首先加上一个额外参数,类型是类对象的一个引用,这个参数用来放置由copy construct 而得到的返回值。
  • 在return命令之前安插一个copy construct调用操作,以便将欲传回的对象的内容当作上述新增参数的初值。

具体过程可以通过下面的伪代码来了解

1
2
3
4
5
6
7
8
Edge get(Edge &_result) {
// 增加一个额外参数作为引用传入函数
Edge point;
//这里会调用Edge的空构造函数
point.Edge::Edge();
//调用拷贝构造函数将Edge将point的值赋给返回值
_result.Edge(point);
}

  在上述代码中,编译器的扩张操作,构造出了point。但是这个没用空构造的point根本就没有起到什么作用。对于编译器来说,这就会导致资源的浪费,拖慢运行速度。所以NRV优化操作就显的很有必要了。

使用NRV优化

1
2
3
4
// g++ -o demo test.cpp
// g++ -o 会对程序做部分优化
Edge construct!
Edge destruct!

  编译器为了避免这个无意义的临时变量,编译器选择使用传入的参数(_ result)。对其直接进行构造,这样就避免了无意义的临时变量。具体操作参考如下伪代码

1
2
3
4
5
Edge get(Edge &_result) { //额外参数作为引用参数介入函数
//使用NRV优化后,不再生成point临时变量,而是对_result直接进行构造
_result.Edge::edge();
return; // 返回已有的返回值
}

结束语

  只有在显式定义copy construct 的时候,编译器才会激活NRV优化操作。对于普通的程序,是看不出NRV优化的好处,只有在庞大数据量时,NRV优化操作的优点才会显现出来。