返回值优化(RVO)和命名返回值优化(NRVO)

本文介绍了C++中的返回值优化(RVO)和命名返回值优化(NRVO),这两种技术通过在函数返回时直接构造对象,避免了复制操作,显著提升程序性能,尤其在处理大型对象时。编译器在满足特定条件时会自动应用这些优化,但并非强制执行,如多返回路径或条件依赖可能导致NRVO失效。
摘要由CSDN通过智能技术生成

        返回值优化(RVO)和命名返回值优化(NRVO)是C++编译器的两种优化技术,它们可以减少或消除函数返回对象时的不必要的复制操作。这些优化对于提高C++程序的性能非常关键,特别是在涉及大型对象或复制操作成本较高时。

1. 返回值优化(RVO)

        RVO是指在函数中直接构造返回值到调用函数的返回位置的优化。也就是说,编译器会将函数的返回值直接在调用者的上下文中构造,从而避免了复制或移动构造函数的调用。RVO最常见的情况是当函数返回一个临时对象时。

MyClass createMyClass() {
    return MyClass();
}

int main() {
    MyClass obj = createMyClass();
}

        在这个例子中,如果编译器应用了RVO,那么`MyClass`类型的对象将直接在`obj`的存储位置被构造,而不是首先在`createMyClass`的局部作用域内构造然后复制或移动到`obj`。

 疑问小插曲

       在上述代码中,如果编译器应用了返回值优化(RVO),那么只会构造一个`MyClass`对象。这个对象将直接在`main`函数中的`obj`变量的位置被构造,而不是先在`createMyClass`函数中构造然后再拷贝或移动到`obj`。这意味着实际上完全避免了拷贝或移动构造函数的调用。

        如果没有优化,那么在C++11之前的语言标准中,可能会进行如下操作:

1. 在`createMyClass`函数中构造一个`MyClass`对象(临时对象)。
2. 通过拷贝构造函数将临时对象拷贝到`main`中的`obj`对象。
3. 销毁临时对象。

        然而,在C++11及以后,即使RVO没有发生,移动语义也可以被利用。这样的话,情况会是这样:

1. 在`createMyClass`函数中构造一个`MyClass`对象(临时对象)。
2. 通过移动构造函数将临时对象的资源“移动”到`main`中的`obj`对象。
3. 销毁临时对象,但由于资源已经被移动,销毁的成本很低。

        然而,实际上现代编译器在这种情况下非常可能应用RVO,所以即使你定义了移动构造函数,它也可能不会被调用,因为只构造了一个对象,没有发生移动。

        在语言标准层面,自C++17起,RVO已经成为强制行为(在满足特定条件时)——如果在`createMyClass`函数返回时可以直接构造`MyClass`对象,编译器必须省略拷贝或移动。这就意味着在C++17或更高版本的标准下,上述代码保证只构造一个`MyClass`对象。

2. 命名返回值优化(NRVO)

        NRVO则是RVO的一个特殊情况,它适用于当函数返回一个具名的局部对象时。编译器会尝试消除这个局部对象和接收对象之间的复制或移动操作。

例如:

MyClass createMyClass() {
    MyClass result;
    // 对result进行一些操作
    return result;
}

int main() {
    MyClass obj = createMyClass();
}

        如果编译器应用了NRVO,`result`对象会直接在`obj`的位置被构造,而不是先在函数`createMyClass`中构造然后复制或移动。

注意点:

        1. RVO和NRVO是编译器选择性进行的优化,标准并不保证一定会发生,但大多数现代编译器(如GCC和Clang)在优化开启时会尽可能利用这些优化技术。

        2. 自C++17起,标准规定了“强制返回值优化”,这意味着在某些情况下编译器必须执行RVO,否则代码将不合法。这通常发生在返回临时对象时。

        3. 如果函数有多个返回路径,并且每个返回路径返回不同的局部对象,NRVO就无法应用。同样,如果返回的对象取决于条件,NRVO可能也无法应用。            

        1: 多个返回路径返回不同的局部对象

MyClass createMyClass(bool cond) {
    MyClass obj1;
    MyClass obj2;
    if (cond) {
        return obj1;  // 返回路径 1
    } else {
        return obj2;  // 返回路径 2
    }
}

        在这个例子中,函数`createMyClass`有两个不同的返回路径,每个路径返回不同的局部对象。在这种情况下,编译器可能无法确定在函数入口就在调用者的栈上预留空间并构造对象的位置,因为直到运行时才能知道应该返回哪个对象。这样,编译器就无法应用NRVO优化,因为它不能预先知道应该优化哪一个对象。

        2: 返回的对象取决于条件

MyClass createMyClass(bool cond) {
    MyClass obj1;
    if (cond) {
        MyClass obj2;
        // ... 对 obj2 进行操作
        return obj2;  // 条件返回路径
    }
    // ... 对 obj1 进行操作
    return obj1;  // 默认返回路径
}

        在这个例子中,返回的对象取决于传递给函数的条件。如果条件为真,将返回`obj2`,否则返回`obj1`。由于`obj2`是在条件块内部定义的,它的生命周期仅限于该块内。因此,编译器无法在函数开始时就在栈上为`obj2`预留空间,因为`obj2`可能根本就不会被构造。这样,编译器同样无法应用NRVO优化。

        NRVO最好的使用场景是一个函数只有一个返回路径,或者虽然有多个返回路径,但每个路径返回的是同一个局部命名对象。当函数中有多个局部对象,并且根据不同的条件返回不同的对象时,编译器很难进行这种优化,因为编译器必须在函数执行时才能确定返回哪个对象。这就需要在运行时进行选择,而这个选择过程无法通过在编译时就将对象直接构造在目标位置来优化。

        自C++17起,标准要求编译器在可能的情况下执行RVO。但对于NRVO,即使在C++17及以后,如果存在多个返回路径,标准并不保证编译器一定能够应用NRVO。因此,当编写可能返回多个局部对象的函数时,仍然需要注意潜在的性能影响。

        RVO和NRVO不仅减少了不必要的复制,还可能减少了内存分配和释放操作,这对于性能至关重要。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值