国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

std::move和std::forward

2019-11-06 06:12:51
字體:
來源:轉載
供稿:網友

轉自coolmeme

通過了解std::move和std::forward不做什么來理解它們很有用。std::move不移動任何東西,std::forward也不轉移任何東西。在運行時(runtime),他們什么都不做,一行代碼也不產生。

std::move和std::forward僅僅是進行類型轉換的函數(實際上是函數模板)。std::move無條件的將其參數轉換為右值,而std::forward只在必要情況下進行這個轉換,就是這樣。這個解釋會引起一系列問題,但是基本上這就是完整的故事。

為了讓故事更加具體,這有一個在C++11中std::move的模擬實現:

template<typename T>                                   // in namespace std

typename remove_reference<T>::type&&

move(T&& param)

{

    using ReturnType =                                            // alias declaration;

        typename remove_reference<T>::type&&;    // see Item 9

    return static_cast<ReturnType>(param);

}

我高亮了兩處代碼。一個是函數的名字,因為返回值很繁瑣,我怕你失去忍耐。另一處是函數的精華本質部分:轉換。如你所見,std::move的參數為一個對象的引用(統一引用,詳見條款24),并且返回的也是該對象的引用。

函數返回值的“&&”部分表示std::move返回了一個右值引用,但是如條款28所述,假如類型T碰巧是個左值引用,T&&就會成為一個左值引用。為阻止這個發生,一個type trait(見條款9)std::remove_reference被用在T上,這樣可以確保“&&”可以應用在一個不是引用的類型上,這個很重要,因為函數返回的右值引用必須是右值。于是,std::move將其參數轉換右值,這就是它所有做的事情。

此外,std::move在c++14中的實現顯得更簡短。由于函數返回類型推導(條款3)和標準庫里的別名模板std::remove_reference_t (見條款9),std::move可以這樣實現:

template<typename T>                     // C++14; still indecltype(auto) move(T&& param)    // namespace std{    using ReturnType = remove_reference_t<T>&&;    return static_cast<ReturnType>(param);}

看上去更簡單些,不是?

因為std::move除了把參數轉換為右值以外不做別的事情,有建議給它一個更好的名字也許類似rvalue_cast,即使如此,我們現在擁有的名字是std::move,所以認識到std::move做什么和不做什么很重要。它做的是轉換,不做移動。

當然,右值適合被移動,所以std::move應用在一個對象上時就是告訴編譯器對象可以被移動。這就是為什么std::move擁有這樣的名字:使得指定可以被移動的對象更容易些。

事實上,右值是僅有的可以被移動的對象。假如你寫了個類代表注釋(annotation),類的構造函數使用std::string類型做為參數表示注釋內容,并且拷貝參數到一個成員變量里。根據條款41的信息,你聲明了一個傳值參數:

class Annotation {public:  explicit Annotation(std::string text); // param to be copied,

  …                                                    // so per Item 41,};                                                       // pass by value

但是Annotation的構造函數僅僅需要讀取text的值,不需要改變它。根據我們固有的傳統,盡可能的使用const,你改變了聲明如下:

class Annotation {public:  explicit Annotation(const std::string text)  …};

為了避免當拷貝text到數據成員中時消耗一次拷貝操作,你使用了條款41的建議,將std::move應用到text上,于是產生了一個右值:

class Annotation {public:  explicit Annotation(const std::string text)  : value(std::move(text))                                      // "move" text into value; this code  { … }                                                                   // doesn't do what it seems to!  …PRivate:  std::string value;};

代碼編譯鏈接都可以正常,也把設置了數據成員為text的內容。唯一使得這段代碼和你想象中的完美實現不一樣的地方是text不是移動到value的,是拷貝的。當然,text是通過std::move轉換成一個右值,但是text是被聲明成const std::string,所以在轉換前,text是一個左值的const std::string,轉換的結果是一個右值const std::string,最終常量性保留了下來。

考慮下當編譯器必須決定哪個std::string構造函數必須調用時的效果,有兩個可能:

class string {         // std::string is actually apublic:                  // typedef for std::basic_string<char>    …    string(const string& rhs); // copy ctor 拷貝構造函數    string(string&& rhs);        // move ctor move移動構造函數    …};

在Annotation的構造函數的成員初始化列表里,std::move(text)的結果是一個類型為const std::string的右值。這個右值不能傳遞給std::string的move構造函數,因為move構造函數需要一個指向非常量std::string的右值引用作為參數。然而這個右值可以傳遞給拷貝構造函數,因為指向常量的左值引用是允許綁定到一個常量右值的。于是成員初始化就會調用std::string的拷貝構造函數,即使text已經轉換成一個右值。這樣的行為是維持常量正確性的一個必需,因為移動一個對象到另一個值通常會改變這個對象,所以語言就不應該允許常量對象被傳遞給一個能修改他們的函數(比如move構造)。

從這個例子可以學到兩個教訓。第一,假如你想對象能夠被移動,不要聲明對象為const,在const對象上的移動操作默默的被轉換成了拷貝操作。第二,std::move不僅不移動任何東西,甚至不能保證被轉換的對象可以被移動。唯一可以確認的是應用std::move的對象結果是個右值。

std::forward的故事和std::move的故事很類似,但std::move是無條件的轉換其參數為一個右值,而std::forward是在某些特定條件下進行轉換。std::forward是一個有條件轉換。為了理解什么時候轉換什么時候不轉換,回憶一下std::forward是怎么使用的。最常見的場景是在函數模板,參數是一個統一引用參數,這個參數會被傳遞給另一個函數。

void process(const Widget& lvalArg);        // process lvalues

void process(Widget&& rvalArg);           // process rvaluestemplate<typename T>                           // template that passesvoid logAndProcess(T&& param)            // param to process{auto now =                                               // get current timestd::chrono::system_clock::now();makeLogEntry("Calling 'process'", now);process(std::forward<T>(param));}

考慮兩種調用logAndProcess的情形,一種是左值,一種是右值:

Widget w;

logAndProcess(w);                   // call with lvalue

logAndProcess(std::move(w)); // call with rvalue

在logAndProcess內部,參數param被傳遞給函數process,process被重載為左值和右值。當我們通過左值去調用logAndProcess時,我們很自然的期望那個左值可以同樣作為一個左值轉移到process函數,當我們通過右值去調用logAndProcess時,我們期望重載的右值函數可以被調用。

但是param,像所有函數參數一樣,是個左值。在logAndProcess內部每一個對process的調用會調起左值重載。為了避免這個,我們需要一個機制來把param轉換成一個右值,當且僅當傳入的用來初始化parm的實參(就是傳遞到logAndProcess的參數)是個右值。

你可能疑惑std::forward是怎么知道它的參數是通過一個右值來初始化的。比如在以上代碼中,std::forward是怎么知道param是被一個左值或右值來初始化呢?簡單的回答是這個信息被編碼到logAndProcess的模板參數T里面。這個參數被傳遞給std::forward,然后恢復了編碼的信息。詳細的描述見條款28。

既然std::move和std::forward都歸結為轉換,唯一不同就是std::move始終進行轉換,而std::forward僅僅有時候轉換,你可能會問我們是否可以廢棄std::move,而只用std::forward。從純技術角度來看,答案是肯定的:std::forward可以做到,std::move不是必須的。當然函數也不是必須的,我們可以寫轉換代碼,但是我希望我們會覺得那樣比較惡心。

std::move吸引人之處在于方便,減少了錯誤的可能性,而且更加清晰。考慮一個類,可以跟蹤我們使用了多少次的move構造函數。我們所需要的是一個靜態變量的計數器,在move構造函數使用時增加。假設類里面的唯一一個非靜態數據成員是std::string,這里有個方便的方法(也就是利用std::move)去實現move構造函數:

class Widget {public:    Widget(Widget&& rhs)    : s(std::move(rhs.s))    { ++moveCtorCalls; }    …

private:    static std::size_t moveCtorCalls;    std::string s;};

用std::forward來實現相同的行為,代碼可能如下:

class Widget {public:Widget(Widget&& rhs)                      // unconventional,: s(std::forward<std::string>(rhs.s))  // undesirable{ ++moveCtorCalls; }                        // implementation…};

注意首先std::move只需要一個函數參數(rhs.s),而std::forward既需要一個函數參數(rhs.s),又需要一個模板類型參數(std::string)。然后我們注意到我們傳遞給std::forward的參數類型必須一個非引用的,因為編碼規范上說明了傳遞的參數必須是一個右值(見條款28)。std::move需要更少的打字,and it spares us the trouble of passing a type argument。也減少了我們傳遞一個錯誤類型(比如std::string&,這會導致數據成員s被拷貝構造而不是move構造)的可能性。

更重要的是,使用std::move會無條件轉換為一個右值,而使用std::forward只會把是綁定到右值引用的參數轉換成右值。這是兩個非常不同的行為。第一個是典型的move,而第二個是僅僅傳遞(轉移)一個對象到另一個函數,通過這種辦法來保留對象原始的左值性或右值性。因為兩者如此不同,所以最好我們使用不同的函數(名)來區分它們。

                                          要記住的事情

1.std::move執行一個無條件的轉化到右值。它本身并不移動任何東西;

2.std::forward把其參數轉換為右值,僅僅在那個參數被綁定到一個右值時;

3.std::move和std::forward在運行時(runtime)都不做任何事。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 五台县| 类乌齐县| 巴中市| 麦盖提县| 台南市| 栾城县| 张家口市| 秦安县| 绍兴县| 百色市| 呼和浩特市| 清水河县| 葫芦岛市| 象山县| 连平县| 广东省| 黔江区| 平舆县| 洮南市| 茶陵县| 三门县| 榆中县| 台江县| 洛宁县| 宜川县| 阿拉善盟| 莱阳市| 南岸区| 苍山县| 黄平县| 大方县| 靖宇县| 策勒县| 正宁县| 江川县| 高要市| 新乐市| 安康市| 叶城县| 阳朔县| 昔阳县|