返回值优化 RVO (return value optimization) 命名返回值优化 NRVO (named return value optimization)
这两个的关系应该是RVO包括NRVO(有命名对象的返回值优化 和无命名对象的返回值优化) 下面主要讨论的是NRVO
1.NRVO
#include <cstdio>
#include <iostream>
using namespace std;
class TestClass {
public:
TestClass() { cout << "TestClass()" << endl; }
TestClass(int v) { cout << "TestClass(int v)" << endl; }
~TestClass() { cout << "~TestClass()" << endl; }
TestClass(const TestClass &obj) {
this->v = obj.v;
cout << "TestClass(const TestClass &obj)" << endl;
}
int v;
};
TestClass GetObj(int v) {
TestClass t;
if (v == 1) {
return t;
}
return t;
}
TestClass GetObj2(int v) {
TestClass t1;
if (v == 1) {
return t1;
}
TestClass t2;
return t2;
}
int main() {
cout << "support rvo:" << endl;
TestClass t = GetObj(1);
cout << endl;
cout << "not support rvo:" << endl;
TestClass t2 = GetObj2(1);
cout << endl;
return 0;
}
g++ test.cpp -o main 对于这个例子: GetObj2不能进行返回值优化: 一次构造函数(TestClass t1;) 一次(外部赋值时)拷贝构造 (TestClass t2 = GetObj2(1);) 一次析构函数 (TestClass t2;;在函数结束后)
对于GetObj1能进行返回值优化,所以仅有一次构造,消除了拷贝构造函数
NRVO的原则: 函数返回的是同一个命名对象(GetObj2里返回了t1 和 t2 不是同一个,无法优化)
2.RVO的其他场景
为什么上面说RVO包括NRVO: 因为RVO还包括对于非命名对象的返回值优化: 下面这个例子:
#include <cstdio>
#include <iostream>
using namespace std;
class TestClass {
public:
TestClass() { cout << "TestClass()" << endl; }
TestClass(int v) { cout << "TestClass(int v)" << endl; }
~TestClass() { cout << "~TestClass()" << endl; }
TestClass(const TestClass &obj) {
this->v = obj.v;
cout << "TestClass(const TestClass &obj)" << endl;
}
int v;
};
TestClass GetObj(int v) {
TestClass t;
if (v == 1) {
return t;
}
return t;
}
TestClass GetObj3(int v) {
if (v == 1) {
return TestClass();
}
return TestClass(1);
}
int main() {
cout << "support rvo:" << endl;
TestClass t = GetObj(1);
cout << endl;
cout << "support rvo:" << endl;
TestClass t3 = GetObj3(1);
cout << endl;
return 0;
}
返回的是没有命名的对象,直接调构造函数,哪怕调的构造函数不是同一个,都是可以进行返回值优化的
3.命名对象和非命名对象共存场景
#include <cstdio>
#include <iostream>
using namespace std;
class TestClass {
public:
TestClass() { cout << "TestClass()" << endl; }
TestClass(int v) { cout << "TestClass(int v)" << endl; }
~TestClass() { cout << "~TestClass()" << endl; }
TestClass(const TestClass &obj) {
this->v = obj.v;
cout << "TestClass(const TestClass &obj)" << endl;
}
int v;
};
TestClass GetObj(int v) {
TestClass t;
if (v == 1) {
return t;
}
return t;
}
TestClass GetObj4(int v) {
TestClass t;
if (v == 1) {
return t;
}
return TestClass(1);
}
int main() {
cout << "support rvo:" << endl;
TestClass t = GetObj(1);
cout << endl;
cout << "not support rvo:" << endl;
TestClass t4 = GetObj4(1);
cout << endl;
return 0;
}
此例子中 GetObj4既返回了命名对象t ,也返回了非命名对象TestClass(1); 这种场景是不能进行返回值优化的
4.总结
实际开发中,我们要避免GetObj2 和GetObj4的情况 即: 不要返回不同的命名对象、 不要命名对象、非命名对象混着在一个函数的不同分支中返回 就可以进行返回值优化的
p.s: 返回值优化是默认开启的,好像是在c++11之后的版本。有个编译选项可以关闭。有兴趣可以看看关闭后的表现。 (g++ test.cpp -o main -fno-elide-constructors)
5.有何意义?
理解返回值优化,有助于帮助我们写出更高效的代码。 比如说:optional 有时候我们使用optional,一旦用的不小心,就会出现不能进行返回值优化的情况
举个例子:在工作中检视其他同事的代码发现了这种写法
std::optional<Entity> Repo::Find(uint32_t ctpGemId) const
{
auto opt = CtpGemEntityEncap().GetRecord(ctpGemId);
if (!opt) {
return {};
}
return std::make_optional<CtpGemEntity>(*opt);
}
修改为支持返回值优化的方式:
std::optional<Entity> Repo::Find(uint32_t idx) const
{
std::optional<Entity> entity;
std::optional<EntityRecord> opt = EntityEncap().GetRecord(idx);
if (!opt) {
return entity;
}
entity.emplace(*opt);
return entity;
}
另外optional也是一个需要很小心使用的容器。后续再发一篇单独写一下optional的注意事项
|