| 網(wǎng)站首頁 | 關(guān)于我們 | 開發(fā)優(yōu)勢 | 產(chǎn)品展示 |
| 合作企業(yè) | 新聞動態(tài) | 聯(lián)系我們 | 電話聯(lián)系 |
文章作者:濟南軟件開發(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)理。