九九热这里直有精品,1区二区三区在线播放,玖玖爱在线观看资源,国产aⅴ综合网,午夜福利男女,日本亚洲欧美三级,日韩无码黄色导航,内射少妇13区,中文字幕高清网

您身邊的軟件定制專家--9年開發(fā)經(jīng)驗為您護航

18678812288
0531-88887250

c++11中的move與forward

文章作者:濟南軟件開發(fā) 時間:2016年12月20日

一. move

關(guān)于lvaue和rvalue, 在c++11以前存在一個有趣的現(xiàn)象:T& 指向lvalue, const T&即可以指向lvalue也可以指向rvalue。

 

但就是沒有一種引用類型,可以限制為只指向rvalue.

 

這乍起來好像也不是很大問題,但事實上這個缺陷在有些時候嚴重的限制了我們在某些情況下,寫出更有效率的代碼。

 

舉個粟子,假設(shè)我們有一個類,它包含了一些資源:

 

復(fù)制代碼

class holder

{

     public:

 

          holder()

          {

               resource_ = new Resource();

          }

          ~holder()

          {

               delete resource_;

          }

 

          holder(const holder& other)

          {

                resource_ = new Resource(*other.resource_);

          }

 

          holder(holder& other)

          {

                resource_ = new Resource(*other.resource_);

          }

 

          holder& operator=(const holder& other)

          {

                delete resource_;

                resource_ = new Resource(*other.resource_);

                return *this;

          }

           holder& operator=(holder& other)

          {

                delete resource_;

                resource_ = new Resource(*other.resource_);

                return *this;

          }

          private:

 

               Resource* resource_;

};

復(fù)制代碼

 

 

這是個RAII的類,構(gòu)造函數(shù)與析構(gòu)函數(shù)分別負責(zé)資源的獲取與釋放,因此也相應(yīng)處理了拷貝構(gòu)造函數(shù)(copy constructor)和重載賦值操作符(assignment operator)。

 

現(xiàn)在假設(shè)我們這樣來使用這個類。

 

// 假設(shè)存在如一個函數(shù),返回值為holder類型

holder get_holder();

 

holder h;

 

h = get_holder();

這小段代碼的最后一條語句做了3件事情:

 

1) 銷毀h中的資源。

 

2) 拷由get_holder()返回的資源。

 

3) 銷毀get_holder()返回的資源。

 

我們顯然可以發(fā)現(xiàn)這其中做了些不是很有必要的事情,假如我們可以直接交換h中的資源與get_holder()返回的資源,那這樣我們可以直接省掉第二步中的拷貝動作了。

 

而這里之所以交換能達到相同的效果,是因為get_holder()返回的是臨時的變量,是個rvalue,它的生命周期通常來說很短,具體在這里,就是賦值語句完成之后,任何人都沒法再引用該rvalue,它馬上就要被銷毀了。

 

如果是像下面這樣的用法,我們顯然不可以直接交換兩者的資源:

 

holder h1;

holder h2;

 

h1 = h2;

因為h2是個lvalue,它的生命周期較長,在賦值語句結(jié)束之后,變量還要存在,還有可能要被別的地方使用。

 

顯然,rvalue的短生命周期給我們提供了在某些情況優(yōu)化代碼的可能。

 

但這種可能在c++11以前是沒法利用到的,因為:我們沒法在代碼中對rvalue區(qū)別對待,在函數(shù)體中,無法分辨?zhèn)鬟M來的參數(shù)到底是不是rvalue,缺少一個rvalue的標記。

 

回憶一下 T& 指向的是lvalue,而const T&指向的,卻可能是lvalue或rvalue,沒法區(qū)分!

 

為了解決這個問題,c++11中引入了一個新的引用類型:T&&

 

這種引用指向的變量是個rvalue, 有了這個引用類型,我們前面提到的問題就迎刃而解了。

 

復(fù)制代碼

class holder

{

     public:

 

          holder()

          {

               resource_ = new Resource();

          }

          ~holder()

          {

               if (resource_) delete resource_;

          }

 

          holder(const holder& other)

          {

                resource_ = new Resource(*other.resource_);

          }

 

          holder(holder& other)

          {

                resource_ = new Resource(*other.resource_);

          }

          

          holder(holder&& other)

          {

                resource_ = other.resource_;

                other.resource_ = NULL;

          }

 

          holder& operator=(const holder& other)

          {

                delete resource_;

                resource_ = new Resource(*other.resource_);

          return *this;

          }

 

          holder& operator=(holder& other)

          {

                delete resource_;

                resource_ = new Resource(*other.resource_);

                return *this;

          }

 

          holder& operator=(holder&& other)

          {

                std::swap(resource_, other.resource_);

                return *this;

          }

 

          private:

 

               Resource* resource_;

};    

復(fù)制代碼

這時我們再寫如下代碼的時候:

 

holder h1;

holder h2;

 

h1 = h2; //調(diào)用operator(holder&);

h1 = get_holder(); //調(diào)用operator(holder&&)

顯然后面的實現(xiàn)是更高效的。

 

 

 

寫到里,有的人也許提出問題: T&& ref 指向的是右值,那ref本身是左值還是右值?具體來說就是:

 

1 holder& operator=(holder&& other)

2 {

3      holder h = other;//這里調(diào)用的是operator=(holder&) 還是operator=(holder&&)?

4      return *this;

5 }

這個問題的本質(zhì)還是怎么區(qū)分rvalue?

 

c++11中對rvalue作了明確的定義:

 

Things that are declared as rvalue reference can be lvalues or rvalues. The distinguishing criterion is: if it has a name, then it is an lvalue. Otherwise, it is an rvalue.

如果一個變量有名字,它就是lvalue,否則,它就是rvalue。

 

根據(jù)這樣的定義,上面的問題中,other是有名字的變量,因此是個lvalue,因此第3行調(diào)用的是operator=(holder&).

 

 

 

好了說這么久,一直沒說到move(),現(xiàn)在我們來給出定義:

 

c++11中的move()是這樣一個函數(shù),它接受一個參數(shù),然后返回一個該參數(shù)對應(yīng)的rvalue().

就這么簡單!你甚至可以暫時想像它的原型是這樣的(當(dāng)然是錯的,正確的原型我們后面再講)

 

T&& move(T& val);

 

 

那么,這樣一個move(),它有什么使用呢?用處大了!

 

前面用到了std::swap()這個函數(shù),回想一下以前我們是怎么想來實現(xiàn)swap的呢?

 

1 void swap(T& a, T& b)

2 {

3     T tmp = a;

4     a = b;

5     b = tmp;

6 }

想像一下,如果T是我們之前定義的holder,這里面多做了多少無用功啊,每一個賦值語句,就有一次資源銷毀,以及一次拷貝!但如果用上了move().

 

1 void swap(T& a, T& b)

2 {

3     T tmp=move(a);

4     a = move(b);

5     b = move(tmp);

6 }

這樣一來,如果holder提供了operator=(T&&)重載, 上述操作就完全只是交換了3次指針,效率大大提升!

 

move使得程序員在有需要的情況下,能夠把lvalue當(dāng)成rvalue來使用。

 

 

 

二. forward()

1.轉(zhuǎn)發(fā)問題

除了move()語義之外,rvalue的提出還為了解決另一個問題:轉(zhuǎn)發(fā)(forward).

 

假設(shè)我們有這樣一個模板函數(shù),它的作用是:緩存一些object,必要的時候,創(chuàng)建新的。

 

復(fù)制代碼

template<class TYPE, class ARG>

TYPE* acquire_obj(ARG arg)

{

     static list<TYPE*> caches;

     TYPE* ret;

 

     if (!caches.empty())

     {

          ret = caches.pop_back();

          ret->reset(arg);

          return ret;

     }

 

     ret = new TYPE(arg);

     return ret;

}

復(fù)制代碼

這個模板函數(shù)的作用簡單來說,就是轉(zhuǎn)發(fā)一下參數(shù)arg給TYPE的reset()函數(shù)和構(gòu)造函數(shù),除此它就沒有再干別的事情,在這個函數(shù)當(dāng)中,我們用了值傳遞的方式來傳遞參數(shù),顯然是比較低效的,多了次無必要的拷貝。

 

于是我們準備改成傳遞引用的方式,同時考慮到要能接受rvalue作為參數(shù),于是改成這樣:

 

template<class TYPE, class ARG>

TYPE* acquire_obj(const ARG& arg)

{

    //...

}

這樣寫其實很不靈活:

 

1)首行,如果reset() 或TYPE的構(gòu)造函數(shù)不接受const類型的引用,那上述的函數(shù)就不能使用了,必須另外提供非const TYPE&的版本,參數(shù)一多的話,很麻煩。

 

2)其次,如果reset()或TYPE的構(gòu)造函數(shù)能夠接受rvalue作為參數(shù)的話,這個特性在acquire_obj()里頭永遠也用不上。

 

 

 

其中1)好理解,2)是什么意思?

 

2)說的是這樣的問題,即使TYPE存在TYPE(TYPE&& other)這樣的構(gòu)造函數(shù),它在acquire_obj()中也永遠不會被調(diào)用,原因是在acquire_obj中,傳遞給TYPE構(gòu)造函數(shù)的,永遠是lvalue.

 

哪怕外面調(diào)用acquire_obj()時,傳遞的是rvalue。

 

holder get_holder();

 

holder* h = acquire_obj<holder, holder>(get_holder());

雖然在上面的代碼中,我們傳遞給acquire_obj的是一個rvalue,但是在acuire_obj內(nèi)部,我們再使用這個參數(shù)時,它卻永遠是lvalue,因為它有名字。

 

acquire_obj這個函數(shù)它的基本功能只是傳發(fā)一下參數(shù),理想狀況下它不應(yīng)該改變我們傳遞參數(shù)的類型:假如我們傳給它lvalue,它就應(yīng)該傳lvalue給TYPE,假如我們傳rvalue給它,它就應(yīng)該傳rvalue給TYPE,但上面的寫法卻沒有做到這點,而在c++11以前也沒法做到。

 

forward()函數(shù)的出現(xiàn),就是為了解決這個問題。

 

forward()函數(shù)的作用:它接受一個參數(shù),然后返回該參數(shù)本來所對應(yīng)的類型。

比如說在上述的例子中(暫時省略參數(shù)的原型,后面再介紹):

 

復(fù)制代碼

holder* h = acquire_obj<holder, holder>(get_holder());

//假設(shè) acquire_obj()接受了一個rvalue作為參數(shù),在它的內(nèi)部,

TYPE* acquire_obj(arg)

{

    //arg本來是rvalue,如果我們直接引用,它會被當(dāng)成lvalue來使用。

    //但如果我們用forward()處理一下,我們卻可以得到它的rvalue版本。

    //此處 TYPE的構(gòu)造函數(shù)接受的是一個rvalue。

    TYPE* ret = new TYPE(forward(arg));

}

 

//但如果我們傳給acquire_obj()的是一個lvalue,

holder h1;

//acquire_obj接受了lvalue作為參數(shù)。

acquire_obj<holder,holder>(h1);

 

TYPE* acquire_obj(arg)

{

    //此處,TYPE的構(gòu)造函數(shù)接受的是一個lvalue。

    TYPE* ret = new TYPE(forward(arg));

}

復(fù)制代碼

 

 

2. 二個原則

要理解forward()是怎么實現(xiàn)的,先得說說c++11中關(guān)于引用的二個原則。

 

原則(1):

 

引用折疊原則(reference collapsing rule)

 

1) T& &(引用的引用) 被轉(zhuǎn)化成 T&.

 

2)T&& &(rvalue的引用)被傳化成 T&.

 

3)  T& &&(引用作rvalue) 被轉(zhuǎn)化成 T&.

 

4)  T&& && 被轉(zhuǎn)化成 T&&.

 

 

 

原則(2):

 

對于以rvalue reference作為參數(shù)的模板函數(shù),它的參數(shù)推導(dǎo)也有一個特殊的原則:

 

假設(shè)函數(shù)原型為:

 

template<class TYPE, class ARG>

TYPE* acquire_obj(ARG&& arg);

1)如果我們傳遞lvalue給acquire_obj(), ARG就會被推導(dǎo)為ARG&,因此

 

復(fù)制代碼

ARG arg;

 

acquire_obj(arg)中acquire_obj被推導(dǎo)為

acquire_obj(ARG& &&)

根據(jù)前面說的折疊原則,acquire_obj(ARG& &&)

最后變成

acquire_obj(ARG&)

復(fù)制代碼

2)如果我們傳遞rvalue給acquire_obj(),ARG就會被推導(dǎo)為ARG,因此

 

acquire_obj(get_arg());

則acquire_obj 被推導(dǎo)為 acquire_obj(ARG&&)

 

 

3.結(jié)論

有了這兩個原則,現(xiàn)在我們可以給出最后acquire_obj的原型,以及forward()的原型。

 

復(fù)制代碼

template<class TYPE>

TYPE&& forward(typename remove_reference<TYPE>::type& arg)

{

   return static_cast<TYPE&&>(arg);

}

 

template<class TYPE, class ARG>

TYPE* acquire_obj(ARG&& arg)

{

   return new TYPE(forward<ARG>(arg));

}

復(fù)制代碼

 

 

下面我們驗證一下,上述函數(shù)是否能正常工作,假如我們傳給acquire_obj一個lvalue,根據(jù)上面說的模板推導(dǎo)原則,ARG會被推導(dǎo)為ARG&,我們得到如下函數(shù):

 

復(fù)制代碼

TYPE* acquire_obj(ARG& && arg)

{

   return new TYPE(forward<ARG&>(arg));

}

 

以及相應(yīng)的forward()函數(shù)。

 

TYPE& && 

forward(typename remove_reference<TYPE&>::type& arg)

{

   return static_cast<TYPE& &&>(arg);

}

 

再根據(jù)折疊原則,我們得到如下的函數(shù):

TYPE* acquire_obj(ARG& arg)

{

   return new TYPE(forward<ARG&>(arg));

}

 

以及相應(yīng)的forward()函數(shù)。

 

TYPE& 

forward(typename remove_reference<TYPE&>::type& arg)

{

   return static_cast<TYPE&>(arg);

}

復(fù)制代碼

 

 

 所以,最后在acquire_obj中,forward返回了一個lvalue, TYPE的構(gòu)造函數(shù)接受了一個lvaue, 這正是我們所想要的。

 

 而假如我們傳遞給acquire_obj一個rvalue的參數(shù),根據(jù)模板推導(dǎo)原則,我們知道ARG會被推導(dǎo)為ARG,于是得到如下函數(shù):

 

 

 

復(fù)制代碼

TYPE* acquire_obj(ARG&& arg)

{

   return new TYPE(forward<ARG>(arg));

}

 

以及相應(yīng)的forward()函數(shù)。

 

TYPE&& 

forward(typename remove_reference<TYPE>::type& arg)

{

   return static_cast<TYPE&&>(arg);

}

復(fù)制代碼

最后acquire_obj中forward()返回了一個rvalue,TYPE的構(gòu)造函數(shù)接受了一個rvalue,也是我們所想要的。

 

可見,上面的設(shè)計完成了我們所想要的功能,這時的acquire_obj函數(shù)才是完美的轉(zhuǎn)發(fā)函數(shù)。

 

 

 

三.move的原型

 

 

復(fù)制代碼

template<class T> 

typename remove_reference<T>::type&&

std::move(T&& a)

{

  typedef typename remove_reference<T>::type&& RvalRef;

  return static_cast<RvalRef>(a);

復(fù)制代碼

根據(jù)rvalue引用的模板推導(dǎo)原則和折疊原則,我們很容易驗證,無論是給move傳遞了一個lvalue還是rvalue,最終返回的,都是一個rvalue reference.

 

而這正是move的意義,得到一個rvalue的引用。

 

 

 

看到這里有人也許會發(fā)現(xiàn),其實就是一個cast嘛,確實是這樣,直接用static_cast也是能達到同樣的效果,只是move更具語義罷了。


想要了解更多詳情歡迎來電咨詢18678812288
登陸網(wǎng)址:m.h6244.cn。
聯(lián)系人:王經(jīng)理。

都兰县| 鄂尔多斯市| 通许县| 东乌| 勃利县| 桑植县| 布拖县| 赣州市| 布尔津县| 喜德县| 同德县| 武宣县| 合阳县| 九龙县| 洛南县| 东山县| 富民县| 齐齐哈尔市| 石泉县| 盐山县| 彭水| 巨野县| 和硕县| 逊克县| 神木县| 隆昌县| 祁东县| 云林县| 新营市| 扬中市| 大同市| 兴宁市| 延边| 台南县| 元江| 社会| 宁陕县| 兰考县| 大新县| 蓝田县| 湖北省|