C++ 命名返回值优化(NRVO)

简介:

命名的返回值优化(NRVO),这优化了冗余拷贝构造函数和析构函数调用,从而提高了总体性能。值得注意的是,这可能导致优化和非优化程序之间的不同行为。

下面是代码段1中的一个简单示例,以说明优化及其实现方式:

A MyMethod (B &var)
{
    A retVal;
    retVal.member = var.value + bar(var);
    return retVal;
}

使用上述函数的程序可能具有如下构造:

valA = MyMethod(valB);

从MyMethod返回的值是在valA通过使用隐藏的参数所指向的内存空间中创建的。下面是当我们公开隐藏的参数并显示地显示构造函数和析构函数时的功能:

A MyMethod(A &_hiddenArg, B &var)
{
    A retVal;
    retVal.A::A(); //constructor for retVal
    retVal.member = var.value + var(var);
    _hiddenArg.A::A(retVal); // the copy constructor for A
    return;
return.A::~A(); // destructor for retVal
}

上段代码为不使用NRVO的隐藏参数代码(伪代码)
从上面的代码可以看出,有一些优化的机会。其基本思想是消除基于堆栈的临时值(retVal)并使用隐藏的参数。因此,这将消除基于堆栈的值的拷贝构造函数和析构函数。下面是基于NRVO的优化代码:

A MyMethod(A &_hiddenArg, B &var)
{
    _hiddenArg.A::A();
    _hiddenArg.member = var.value + bar(var);
    Resurn
}

带有NRVO的隐藏参数代码(伪代码)

代码示例
示例1:简单示例

#include <stdio.h>

class RVO
{
public:
        RVO() { printf("I am in constructor\n"); }
        RVO( const RVO& c_RVO ) { printf("I am in copy constructor\n"); }
        ~RVO() { printf("I am in destructor\n"); }
        int mem_var;
};

RVO MyMethod(int i)
{
        RVO rvo;
        rvo.mem_var = i;
        return rvo;
}

int main()
{
        RVO rvo;
        rvo = MyMethod(5);
}

代码:Sample1.cpp
如果没有NRVO,预期的输出将是:

I am in constructor
I am in constructor
I am in copy constructor
I am in destructor
I am in destructor
I am in destructor

使用NRVO,预期的输出将是:

I am in constructor
I am in constructor
I am in destructor
I am in destructor

示例2:更复杂的示例

#include <stdio.h>
class A
{
public:
        A()
        {
                printf("A: I am in constructor\n");
                i = 1;
        }

        ~A()
        {
                printf("A: I am in destructor\n");
                i = 0;
        }

        A(const A& a)
        {
                printf("A: I am in copy constructor\n");
                i = a.i;
        }

        int i, x, w;
};

class B
{
public:
        A a;
        B()
        {
                printf("B: I am in constructor\n");
        }

        ~B()
        {
                printf("B: I am in destructor\n");
        }

        B(const B& b)
        {
                printf("B: I am in copy constructor\n");
        }
};

A MyMethod()
{
        B* b = new B();
        A a = b->a;
        delete b;
        return a;
}

int main()
{
        A a;
        a = MyMethod();
}

代码Sample2.cpp
无NRVO的输出将如下所:

A: I am in constructor
A: I am in constructor
B: I am in constructor
A: I am in copy constructor
B: I am in destructor
A: I am in destructor
A: I am in copy constructor
A: I am in destructor
A: I am in destructor
A: I am in destructor

当NRVO优化启动时,输出将是:

A: I am in constructor
A: I am in constructor
B: I am in constructor
A: I am in copy constructor
B: I am in destructor
A: I am in destructor
A: I am in destructor
A: I am in destructor

优化限制
有些情况下,优化不会真正启动。以下是这些限制的样本
示例3:异常示例
在遇到异常时,隐藏的参数必须在它正在替换的临时范围内被破坏。

// RVO class is defined above in figure 4
#include <stdio.h>
RVO MyMethod(int i)
{
    RVO rvo;
    cvo.mem_var = i;
    throw "I am throwing an exception!";
    return rvo;
}

int main()
{
    RVO rvo;
    try
    {
        rvo = MyMethod(5);
    }
    catch(char* str)
    {
        printf("I caught the exception\n");
    }

代码Sample3.cpp
如果没有NRVO,预期的输出将是:

I am in constructor
I am in constructor
I am in destructor
I caught the exception
I am in destructor

如果“抛出”被注释掉,输出将是:

I am in constructor
I am in constructor
I am in copy constructor
I am in destructor
I am in destructor
I am in destructor

现在,如果“抛出”被注释掉,并且NRVO被触发,输出将如下所示:

I am in constructor
I am in constructor
I am in destructor
I am in destructor

也就是说sample3.cpp在没有NRVO的情况下,会表现出相同的行为。

示例4:不同的命名对象示例
若要使用优化,所有退出路径必须返回同一命名对象。

#include <stdio.h>
class RVO
{
public:
        RVO()
        {
                printf("I am in construct\n");
        }

        RVO(const RVO& c_RVO)
        {
                printf("I am in copy construct\n");
        }

        int mem_var;
};

RVO MyMethod(int i)
{
        RVO rvo;
        rvo.mem_var = i;
        if( rvo.mem_var == 10 )
                return RVO();
        return rvo;
}

int main()
{
        RVO rvo;
        rvo = MyMethod(5);
}

代码Sample4.cpp
启用优化时输出与不启用任何优化相同。NRVO实际上并不发生,因为并非所有返回都返回相同的对象。

I am in constructor
I am in constructor
I am in copy constructor

如果将上面的示例更改为返回rvo。在返回对象时,优化将消除复制构造函数:

#include <stdio.h>
class RVO
{
public:
        RVO()
        {
                printf("I am in constructor\n");
        }

        RVO(const RVO& c_RVO)
        {
                printf("I am in copy constructor\n");
        }

        int mem_var;
};

RVO MyMethod(int i)
{
        RVO rvo;
        if( i == 10 )
                return rvo;
        rvo.mem_var = i;
                return rvo;
}

int main()
{
        RVO rvo;
        rvo = MyMethod(5);
}

代码Sample4_Modified.cpp修改并使用NRVO,输出结果将如下所示:

I am in constructor
I am in constructor

优化副作用
程序员应该意识到这种优化可能会影响应用程序的流程。下面的示例说明了这种副作用:

#include <stdio.h>

int NumConsCalls = 0;
int NumCpyConsCalls = 0;

class RVO
{
public:
        RVO()
        {
                NumConsCalls ++;
        }

        RVO(const RVO& c_RVO)
        {
                NumCpyConsCalls++;
        }
};

RVO MyMethod()
{
        RVO rvo;
        return rvo;
}

int main()
{
        RVO rvo;
        rvo = MyMethod();
        int Division = NumConsCalls / NumCpyConsCalls;
        printf("Construct calls / Copy constructor calls = %d\n", Division);
}

代码段Sample5.cpp
编译未启用优化将产生大多数用户所期望的。“构造函数”被调用两次。“拷贝构造函数”被调用一次。因此除法生成2。

Constructor calls / Copy constructor calls = 2

另一方面,如果上面的代码通过启用优化进行编译,NRVO将会启用。因此“拷贝构造函数”调用将被删除。因此,NumCpyConsCalls将为零,导致异常。如果没有适当处理,可以导致应用程序崩溃。


引入自:https://msdn.microsoft.com/en-us/library/ms364057(v=vs.80).aspx


 本文转自 XDATAPLUS 51CTO博客,原文链接:http://blog.51cto.com/xdataplus/2069825


相关文章
|
2月前
|
存储 缓存 算法
【C/C++ 性能优化】提高C++程序的缓存命中率以优化性能
【C/C++ 性能优化】提高C++程序的缓存命中率以优化性能
310 0
|
2月前
|
存储 缓存 算法
高效编程:我们应该了解哪些编译器优化技术?如何做出成熟的优化行为,掌握C++编程中的编译器优化艺术。
高效编程:我们应该了解哪些编译器优化技术?如何做出成熟的优化行为,掌握C++编程中的编译器优化艺术。
218 5
|
19天前
|
编译器 C++ 开发者
C++一分钟之-返回值优化与Move Semantics
【6月更文挑战第19天】C++的RVO与移动语义提升效率,减少对象复制。RVO是编译器优化,避免临时对象的创建。移动语义通过右值引用和`std::move`转移资源所有权。注意RVO不是总有效,不应过度依赖。使用移动语义时,避免误用`std::move`导致对象无效。示例展示了RVO和移动构造函数的应用。理解并恰当使用这些机制能写出更高效代码。
27 3
|
2月前
|
消息中间件 算法 Java
C++实时通信优化技术探究
C++实时通信优化技术探究
34 3
|
2月前
|
算法 测试技术 数据处理
【C++ 设计思路】优化C++项目:高效解耦库接口的实战指南
【C++ 设计思路】优化C++项目:高效解耦库接口的实战指南
101 5
|
2月前
|
存储 算法 数据管理
C++中利用随机策略优化二叉树操作效率的实现方法
C++中利用随机策略优化二叉树操作效率的实现方法
87 1
|
2月前
|
安全 编译器 程序员
C/C++编译的第一步:深入了解预处理器的力量与优化
C/C++编译的第一步:深入了解预处理器的力量与优化
228 1
|
2月前
|
设计模式 算法 C++
从 C++ 优化状态机实现:结合设计模式的实用指南
从 C++ 优化状态机实现:结合设计模式的实用指南
156 1
|
2月前
|
编译器 C++
【C++练级之路】【Lv.4】类和对象(下)(初始化列表,友元,static成员,编译器的优化)
【C++练级之路】【Lv.4】类和对象(下)(初始化列表,友元,static成员,编译器的优化)
|
1天前
|
C++
C++友元函数和友元类的使用
C++中的友元(friend)是一种机制,允许类或函数访问其他类的私有成员,以实现数据共享或特殊功能。友元分为两类:类友元和函数友元。类友元允许一个类访问另一个类的私有数据,而函数友元是非成员函数,可以直接访问类的私有成员。虽然提供了便利,但友元破坏了封装性,应谨慎使用。
35 9