zoukankan      html  css  js  c++  java
  • C++返回值优化

    返回值优化(Return Value Optimization,简称RVO)是一种编译器优化机制:当函数需要返回一个对象的时候,如果自己创建一个临时对象用于返回,那么这个临时对象会消耗一个构造函数(Constructor)的调用、一个复制构造函数的调用(Copy Constructor)以及一个析构函数(Destructor)的调用的代价。

    经过返回值优化,就可以将成本降低到一个构造函数的代价。这样就省去了一次拷贝构造函数的调用和依次析构函数的调用。

     

    例子如下:

    class MyString {
    public: 
        MyString() { 
            _data = NULL; 
            _len = 0; 
            printf("Constructor is called!
    ");
        } 
        MyString(const char* p) { 
            _len = strlen (p); 
            _init_data(p); 
            cout << "Constructor is called! this->_data: " << (long)_data << endl;
        } 
        MyString(const MyString& str) { 
            _len = str._len; 
            _init_data(str._data); 
            cout << "Copy Constructor is called! src: " << (long)str._data << " dst: " << (long)_data << endl;
        }
        
        ~MyString() { 
            if (_data)
            {
                cout << "DeConstructor is called! this->_data: " << (long)_data << endl; 
                free(_data);
            }
            else
            {
                std::cout << "DeConstructor is called!" << std::endl; 
            }
        } 
        MyString& operator=(const MyString& str) { 
            if (this != &str) { 
                _len = str._len; 
                _init_data(str._data); 
            } 
            cout << "Copy Assignment is called! src: " << (long)str._data << " dst" << (long)_data << endl; 
            return *this; 
        } 
        
        operator const char *() const {
            return _data;
        }
    
        void display() const
        {
            if (_data)
            {
                cout << "str is " << _data << "(" << (long)_data << ")" << endl;
            }
            else
            {
                cout << "nothing" << endl;
            }
        }
    private: 
        char *_data; 
        size_t   _len; 
        void _init_data(const char *s) { 
            _data = new char[_len+1]; 
            memcpy(_data, s, _len); 
            _data[_len] = ''; 
        } 
    }; 
    
    MyString foo1()
    {
        return MyString("123");
    }
    
    MyString foo2()
    {
        MyString str1("456");
        return str1;
    }
    
    int main()
    {
        foo1();
        cout << "--------------------
    ";
    
        foo2();
        cout << "--------------------
    ";
    
        MyString str1 = foo1();
        cout << "--------------------
    ";
    
        MyString str2 = foo2();
        cout << "--------------------
    ";
        return 0;
    }

    函数foo1直接返回一个临时对象,而foo2返回一个局部变量。在没有RVO的情况下,不管是调用foo1还是foo2,实际上都是先调用构造函数,然后调用复制构造函数构造作为返回值的临时对象。而对于str1和str2的构造,还会再次调用一次复制构造函数。上述代码,使用的编译命令为:g++ -fno-elide-constructors -o rvo rvo.cpp

    -fno-elide-constructors选项可以取消编译器的 copy-elision 优化策略。得到的结果如下:

     

    Constructor is called! this->_data: 8949776

    Copy Constructor is called! src: 8949776 dst: 8949808

    DeConstructor is called! this->_data: 8949776

    DeConstructor is called! this->_data: 8949808

    --------------------

    Constructor is called! this->_data: 8949808

    Copy Constructor is called! src: 8949808 dst: 8949776

    DeConstructor is called! this->_data: 8949808

    DeConstructor is called! this->_data: 8949776

    --------------------

    Constructor is called! this->_data: 8949776

    Copy Constructor is called! src: 8949776 dst: 8949808

    DeConstructor is called! this->_data: 8949776

    Copy Constructor is called! src: 8949808 dst: 8949776

    DeConstructor is called! this->_data: 8949808

    --------------------

    Constructor is called! this->_data: 8949808

    Copy Constructor is called! src: 8949808 dst: 8949840

    DeConstructor is called! this->_data: 8949808

    Copy Constructor is called! src: 8949840 dst: 8949808

    DeConstructor is called! this->_data: 8949840

    --------------------

    DeConstructor is called! this->_data: 8949808

    DeConstructor is called! this->_data: 8949776

     

    如果编译时去掉了-fno-elide-constructors选项,则编译器开启RVO,结果如下:

     

    Constructor is called! this->_data: 34054160

    DeConstructor is called! this->_data: 34054160

    --------------------

    Constructor is called! this->_data: 34054160

    DeConstructor is called! this->_data: 34054160

    --------------------

    Constructor is called! this->_data: 34054160

    --------------------

    Constructor is called! this->_data: 34054192

    --------------------

    DeConstructor is called! this->_data: 34054192

    DeConstructor is called! this->_data: 34054160

     

    可见开启了RVO之后,省略了不必要的复制拷贝,开启RVO之后,函数是直接在接收返回值的地方直接构造对象。

     

    实际上,foo1和foo2分别对应了RVO和NRVO(Named Return Value Optimization)。具名返回值优化(NRVO),是对于按值返回“具名对象”(就是有名字的变量)时的优化手段,其实道理是一样的,但由于返回的值是具名变量,情况会复杂很多。所以,能执行优化的条件更苛刻。比如函数中,在不同的返回路径上返回不同名的对象,就不会执行NRVO。

    比如下面的代码:

    MyString bar1(int n)
    {
        if (n > 2)
        {
            return MyString("abc");
        }
        else
        {
            return MyString("ABC");
        }
    }
    
    MyString bar2(int n)
    {
        MyString str1("abc");
        MyString str2("ABC");
        if (n > 2)
        {
            return str1;
        }
        else
        {
            return str2;
        }
    }
    
    int main(int argc, char **argv)
    {
        bar1(1);
        cout << "--------------------
    ";
    
        bar2(1);
        cout << "--------------------
    ";
    
        MyString str1 = bar1(1);
        cout << "--------------------
    ";
    
        MyString str2 = bar2(1);
        cout << "--------------------
    ";
        return 0;
    }

    函数bar1返回临时对象,bar2返回具名对象,也就是说,如果执行优化的话,bar1执行RVO,而bar2执行NRVO。

    首先是加上-fno-elide-constructors选项后的运行结果:

     

    Constructor is called! this->_data: 11149328

    Copy Constructor is called! src: 11149328 dst: 11149360

    DeConstructor is called! this->_data: 11149328

    DeConstructor is called! this->_data: 11149360

    --------------------

    Constructor is called! this->_data: 11149360

    Constructor is called! this->_data: 11149328

    Copy Constructor is called! src: 11149328 dst: 11149392

    DeConstructor is called! this->_data: 11149328

    DeConstructor is called! this->_data: 11149360

    DeConstructor is called! this->_data: 11149392

    --------------------

    Constructor is called! this->_data: 11149392

    Copy Constructor is called! src: 11149392 dst: 11149360

    DeConstructor is called! this->_data: 11149392

    Copy Constructor is called! src: 11149360 dst: 11149392

    DeConstructor is called! this->_data: 11149360

    --------------------

    Constructor is called! this->_data: 11149360

    Constructor is called! this->_data: 11149328

    Copy Constructor is called! src: 11149328 dst: 11149424

    DeConstructor is called! this->_data: 11149328

    DeConstructor is called! this->_data: 11149360

    Copy Constructor is called! src: 11149424 dst: 11149360

    DeConstructor is called! this->_data: 11149424

    --------------------

    DeConstructor is called! this->_data: 11149360

    DeConstructor is called! this->_data: 11149392

     

    加上-fno-elide-constructors选项后,运行结果如下:

     

    Constructor is called! this->_data: 9449488

    DeConstructor is called! this->_data: 9449488

    --------------------

    Constructor is called! this->_data: 9449488

    Constructor is called! this->_data: 9449520

    Copy Constructor is called! src: 9449520 dst: 9449552

    DeConstructor is called! this->_data: 9449520

    DeConstructor is called! this->_data: 9449488

    DeConstructor is called! this->_data: 9449552

    --------------------

    Constructor is called! this->_data: 9449552

    --------------------

    Constructor is called! this->_data: 9449488

    Constructor is called! this->_data: 9449520

    Copy Constructor is called! src: 9449520 dst: 9449584

    DeConstructor is called! this->_data: 9449520

    DeConstructor is called! this->_data: 9449488

    --------------------

    DeConstructor is called! this->_data: 9449584

    DeConstructor is called! this->_data: 9449552

     

    对比上面的结果,可见返回临时对象的bar1函数的调用进行了优化。而bar2函数的调用,不管有没有-fno-elide-constructors选项,单独调用bar2返回结果都是一样的,说明没有执行NRVO。对比”MyString str2 = bar2(1);”语句的执行结果,发现加上-fno-elide-constructors选项选项之后,仅仅少了一次复制构造函数的调用,这是因为虽然bar2没有执行NRVO,但是使用bar2返回的临时对象初始化str2时,编译器依然有copy elision的优化策略。

     

    有关copy elision的解释如下:

    In C++ computer programming, copy elision refers to a compiler optimization technique that eliminates unnecessary copying of objects.

    The standard also describes a few situations where copying can be eliminated even if this would alter the program's behavior, the most common being the return value optimization. Another widely implemented optimization, described in the C++ standard, is when a temporary object of class type is copied to an object of the same type.

    (https://en.wikipedia.org/wiki/Copy_elision)

     

    When a nameless temporary, not bound to any references, would be copied or moved (since C++11) into an object of the same type (ignoring top-level cv-qualification), the copy/move (since C++11) is omitted. When that temporary is constructed, it is constructed directly in the storage where it would otherwise be copied or moved (since C++11) to. When the nameless temporary is the argument of a return statement, this variant of copy elision is known as RVO, "return value optimization".

    (http://en.cppreference.com/w/cpp/language/copy_elision)

     

    注:以上所有代码的编译环境是:操作系统CentOS Linux release 7.3.1611;GCC版本:gcc version 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC)

     

    参考:

    http://blog.csdn.net/gatieme/article/details/22650353

    http://www.cnblogs.com/liyiwen/archive/2009/12/02/1615711.html

  • 相关阅读:
    shiro密码的比对,密码的MD5加密,MD5盐值加密,多个Relme
    shiro权限配置的细节问题&认证
    shiro集成spring&工作流程&DelegatingFilterProxy
    shiro简单入门介绍
    springmvc小结(下)
    springmvc小结(上)
    springmvc(5)拦截器
    spring mvc(4)处理模型数据
    springmvc(3)注解
    深入理解javascript作用域系列第四篇——块作用域
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7308701.html
Copyright © 2011-2022 走看看