c++的RVO

返回值优化 RVO (return value optimization)
命名返回值优化 NRVO (named return value optimization)

这两个的关系应该是RVO包括NRVO(有命名对象的返回值优化 和无命名对象的返回值优化)
下面主要讨论的是NRVO

1.NRVO

#include 
#include 

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
c++的RVO_第1张图片
对于这个例子:
GetObj2不能进行返回值优化:
一次构造函数(TestClass t1;)
一次(外部赋值时)拷贝构造 (TestClass t2 = GetObj2(1);)
一次析构函数 (TestClass t2;;在函数结束后)

对于GetObj1能进行返回值优化,所以仅有一次构造,消除了拷贝构造函数

NRVO的原则:
函数返回的是同一个命名对象(GetObj2里返回了t1 和 t2 不是同一个,无法优化)

2.RVO的其他场景

为什么上面说RVO包括NRVO:
因为RVO还包括对于非命名对象的返回值优化:
下面这个例子:

#include 
#include 

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;
}

c++的RVO_第2张图片
返回的是没有命名的对象,直接调构造函数,哪怕调的构造函数不是同一个,都是可以进行返回值优化的

3.命名对象和非命名对象共存场景

#include 
#include 

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);
这种场景是不能进行返回值优化的
c++的RVO_第3张图片

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的注意事项

你可能感兴趣的