编译器返回值优化(Return Value Optimization,RVO)

编译器返回值优化

返回值优化

返回值优化 (Return Value Optimization, RVO) 是编译器一种抑制拷贝 (Copy Elision) 的优化机制,避免代码发生不必要的拷贝。特别对于返回一些局部创建的大对象来说,有助于提高性能。虽然这是编译器的行为,但是并非所有情况下,编译器都会对返回值进行优化。因此,开发者需要搞清楚何种情况,才会触发此机制。

返回值优化包括两种形式:RVO 和 NRVO (Named RVO)。

RVO:它作用于右值对象,即临时对象。例如在函数中直接创建并返回的对象。RVO 在C++11之前就有了。从c++17以及之后,RVO是编译器的标准原则。

NRVO:它作用于有名字的局部变量。例如在函数中创建的对象然后返回。NRVO是C++11标准引入的。

这两种优化技术都可以在不改变程序语义的前提下提高程序的性能,通过减少数据拷贝和临时变量的创建,可以有效地优化函数的返回值处理。

举例说明

看如下代码:

class Test {
public:
  Test() { std::cout << "Test()" << std::endl; }
  ~Test() { std::cout << "~Test() " << std::endl; }
  Test(const Test &t) { std::cout << "Test(const Test &t)" << std::endl; }
  Test &operator=(const Test &t) {
    std::cout << "Test &operator=(const Test &t)" << std::endl;
    return *this;
  }
};

Test GetTest() {
  Test t;
  return t;
}

int main() {
  Test t = GetTest();
  return 0;
}

上面的代码先不说编译器优化,单纯从代码角度分析,main从开始运行到退出main,整个过程中Test的类成员函数调用顺序应该是:

  1. 调用Test构造函数,生成对象;
  2. 调用Test拷贝构造函数,生成临时对象;
  3. 析构第1步生成的对象;
  4. 调用Test拷贝构造函数,将第2步生成的临时变量拷贝到main()函数中的局部对象t中;
  5. 调用Test析构函数,将第2步生成的临时对象释放;
  6. 调用Test析构函数,释放main()函数中的t局部对象。

好了,现在来看下运行结果:

Test()
~Test() 

看这个运行结果,傻眼了吧,和上面预测的完全不一致,这里就是返回值优化起作用了。

我们可以通过编译参数-fno-elide-constructors禁用返回值优化,看下效果:

g++ -std=c++11 -fno-elide-constructors -g ./src/test6.cc -o test6

Test()
Test(const Test &t)
~Test() 
Test(const Test &t)
~Test() 
~Test()

这次禁用返回值优化后,运行结果和上面我们预测的完全一致了。

返回值优化失效场景

编译器并非万能,在某些场景下,返回值优化也会失效。

不同条件分支,返回不同变量

Test GetTest(bool flag) {
  Test t1;
  Test t2;
  if (flag) {
    return t1;
  }
  return t2;
}

int main() {
  Test t = GetTest(true);
  return 0;
}

返回全局变量

Test g_t;
Test GetTest() { return g_t; }

int main() {
  Test t = GetTest();
  return 0;
}

直接返回函数参数

Test GetTest(Test t) {
  return t;
}

int main() {
  Test t;
  Test t1 = GetTest(t);
  return 0;
}

返回成员变量

class Test1 {
public:
  Test t;
};

Test GetTest() {
  Test1 t1;
  return t1.t;
}

int main() {
  Test t = GetTest();
  return 0;
}

赋值operator=

Test GetTest() { return Test(); }

int main() {
  Test t;
  t = GetTest();
  return 0;
}

使用std::move返回

Test GetTest() {
  Test t;
  return std::move(t);
}

int main() {
  Test t = GetTest();
  return 0;
}

运行结果:

Test()
Test(Test &&t)
~Test() 
~Test() 

场景比较多,也很难记全,所以大道至简,最合适的还是以下面的方式来写:

bool GetTest(Test* output) 

以入参的方式来操作。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值