C++11中的右值引用及move語義編程


C++0x中加入了右值引用,和move函數。右值引用出現之前我們只能用const引用來關聯臨時對象(右值)(造孽的VS可以用非const引用關聯臨時對象,請忽略VS),所以我們不能修臨時對象的內容,右值引用的出現就讓我們可以取得臨時對象的控制權,終於可以修改臨時對象了!而且書上說配合move函數,可以大大提高現有C++的效率。那么是怎樣提高它的效率的呢?看段代碼先!

#include <iostream>
#include <utility>
#include <vector>
#include <string>
int main()
{
    std::string str = "Hello";
    std::vector<std::string> v;
 
    // uses the push_back(const T&) overload, which means 
    // we'll incur the cost of copying str
    v.push_back(str);
    std::cout << "After copy, str is \"" << str << "\"\n";
 
    // uses the rvalue reference push_back(T&&) overload, 
    // which means no strings will copied; instead, the contents
    // of str will be moved into the vector.  This is less
    // expensive, but also means str might now be empty.
    v.push_back(std::move(str));
    std::cout << "After move, str is \"" << str << "\"\n";
 
    std::cout << "The contents of the vector are \"" << v[0]
                                         << "\", \"" << v[1] << "\"\n";
}

Output:

After copy, str is "Hello"
After move, str is ""
The contents of the vector are "Hello", "Hello"

 

看完大概明白一點兒了,加上move之后,str對象里面的內容被"移動"到新的對象中並插入到數組之中了,同時str被清空了。這樣一來省去了對象拷貝的過程。所以說在str對象不再使用的情況下,這種做法的效率更高一些!但問題是str的內容在什么地方被移走的呢?move函數到底是干啥的?扣一下stl源碼吧,下面是move模板的源碼:

// TEMPLATE FUNCTION move
    template<class _Ty> inline
    typename tr1::_Remove_reference<_Ty>::_Type&&
        move(_Ty&& _Arg)
    {    // forward _Arg as movable
    return ((typename tr1::_Remove_reference<_Ty>::_Type&&)_Arg);
    }

 

好吧,看過了這段,可能有人又迷惑了,不是說有名左指變量不能綁定到右值引用上面么?為什么move函數的參數是右值引用卻可以接受左值變量作為參數?難道STL錯了么?事實上,C++0x在引入右值引用的時候對函數模板自動推導也加入了新的規則,簡單的說,像例子中的這種情況,模板參數是_Ty而函數的參數是_Ty&&(右值引用),同時_Arg是string的左值對象的情況下,會觸發一個特殊規則,_Ty會推導成string&,也就是說此事推導出來的函數與move<string&>一致。那么move(_Ty&& _Arg) 得到的應該是move(string& && _Arg)這個時候根據引用折疊原則,會變成這個樣子move(string& _Arg)。詳細的描述參見白雲飄飄翻譯的vc技術文檔(http://www.cppblog.com/kesalin/archive/2009/06/05/86851.html)。函數的返回值嘛,就好說了,就是返回所持有類型的右值引用了。所以,move函數的作用很簡單,不管你給什么參數,都返回對應類型的右值引用!那么,上面例子中str的不是在move函數中被移走的。綜上,我們猜測str內容肯定是在構造新對象的過程中被新對象偷走的,也就是在string的參數為右值引用的構造函數中被偷走的!翻看string的源碼(來自VS實現的STL),果然如此啊!如下:

        basic_string(_Myt&& _Right)
        : _Mybase(_STD forward<_Alloc>(_Right._Alval))
        {    // construct by moving _Right
        _Tidy();
        assign(_STD forward<_Myt>(_Right));
        }

        _Myt& assign(_Myt&& _Right)
        {    // assign by moving _Right
        if (this == &_Right)
            ;
        else if (get_allocator() != _Right.get_allocator()
            && this->_BUF_SIZE <= _Right._Myres)
            *this = _Right;
        else
            {    // not same, clear this and steal from _Right
            _Tidy(true);
            if (_Right._Myres < this->_BUF_SIZE)
                _Traits::move(this->_Bx._Buf, _Right._Bx._Buf,
                    _Right._Mysize + 1);
            else
                {    // copy pointer
                this->_Bx._Ptr = _Right._Bx._Ptr;
                _Right._Bx._Ptr = 0;
                }
            this->_Mysize = _Right._Mysize;
            this->_Myres = _Right._Myres;

            _Right._Tidy();
            }
        return (*this);
        }

 

所以,我們知道了,C++0x在STL模板庫中加入了參數為右值引用的構造函數,用於把參數所關聯對象中的數據移動到新對象當中,避免了深度拷貝,增加了效率。再詳細翻看源碼,可以發現除了構造函數,operator=也重載了一個參數為右值引用的函數,用途和構造函數類似。所以我們自定義中的類也應該增加參數為右值引用的構造函數和重載賦值運算符!原因是啥,看例子!

未定義參數為右值引用的構造函數:

#include <iostream>
#include <utility>
#include <vector>
#include <string>

using namespace std;

class MyPoint{
public:
    MyPoint()
        :comment(""), x(0), y(0)
    {
    }

    MyPoint(const MyPoint& p)
       :comment(p.comment),x(p.x),y(p.y) 
    {}

 //MyPoint(MyPoint&& p) // :comment(move(p.comment)), x(p.x), y(p.y) //{ // p.x = 0; // p.y = 0; //}

    string toString()
    {
        char buf[100];
        sprintf(buf, "%s: %d %d", comment.c_str(), x, y);

        return buf;
    }

    string comment;
    int x;
    int y;

};

int main()
{
    MyPoint p;
    p.comment = "First point";
    p.x = 9;
    p.y = 7;

    vector<MyPoint> v;
 
    v.push_back(p);

    cout << "After copy, str is \"" << p.toString() << "\"\n";
 
    v.push_back(move(p));
    cout << "After move, str is \"" << p.toString() << "\"\n";
 
    cout << "The contents of the vector are \"" << v[0].toString()
                                         << "\", \"" << v[1].toString() << "\"\n";


    cin.get();
}

結果:

After copy, str is "First point: 9 7"
After move, str is "First point: 9 7"
The contents of the vector are "First point: 9 7", "First point: 9 7"

 

定義了參數為右值引用的構造函數之后:

After copy, str is "First point: 9 7"
After move, str is ": 0 0"
The contents of the vector are "First point: 9 7", "First point: 9 7"

 

綜上所述,C++0x中的move語義編程,不僅僅是在應用的時候使用參數中加上move,對於自定義類需要增加參數為右值引用的構造函數和賦值運算符,這種構造函數我們稱為move構造函數!公司里面的c++標准已經更新,要求在定義copy構造函數的同時定義move構造函數,雖然現在這種編程方法沒有流行,但是我相信以后這將成為另外一個媲美引用的優化運行速度的編程方法,我們拭目以待!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM