std::forward與完美轉(zhuǎn)發(fā)

引用

計算機專業(yè)大一的基礎(chǔ)內(nèi)容之一,值傳遞和引用傳遞:

void foo(int& a)
{
    a = 2;
}

void bar(int a)
{
    a = 3;
}

int main()
{
    int a = 1;
    foo(a);
    cout << a << endl;
    bar(a);
    cout << a << endl;
    return 0;
}

這里會輸出兩個2:

2
2

當然用指針可以達到類似的效果古沥,但不完全等價:

void foo(int* a)
{
    *(a) = 2;
}

void bar(int a)
{
    a = 3;
}

int main()
{
    int a = 1;
    foo(&a);
    cout << a << endl;
    bar(a);
    cout << a << endl;
    return 0;
}

用指針太容易出現(xiàn)內(nèi)存問題了娇跟,引用更加安全,指針可能會出現(xiàn)這樣的情況:

void foo(int* a)
{
    int b = 0;
    a = &b;
    *(a) = 2;    //改變了a指向的地址盹沈,所以修改不對main中的a起效
}

void bar(int a)
{
    a = 3;
}

int main()
{
    int a = 1;
    foo(&a);
    cout << a << endl;
    bar(a);
    cout << a << endl;
    return 0;
}

左值引用與右值引用

眾所周知C++允許程序員非常自由地操作內(nèi)存吃谣,為了更加靈活地使用引用,C++11添加了右值引用的特性肃晚。
試想如下場景:

void foo(int a)
{
    bar(a);
}

void bar(const int a)
{
    cout << a << endl;
}

int main()
{
    int a = 2;
    foo(a);
    foo(1);
}

這會導(dǎo)致foo和bar函數(shù)每次都在棧內(nèi)存上新建一個變量仔戈,于是我們加入引用以節(jié)省內(nèi)存:

void foo(int& a)
{
    bar(a);
}

void bar(const int& a)
{
    cout << a << endl;
}

int main()
{
    int a = 2;
    foo(a);
    foo(1);
}

這會導(dǎo)致編譯錯誤,因為參數(shù)int& a是一個左值晋修,而常量1是一個右值凰盔,通過修改int&為const int&可以部分解決這個問題:

void foo(const int& a) // const & binds to everything
{
    bar(a);
}

void bar(const int& a)
{
    cout << a << endl;
}

int main()
{
    int a = 2;
    foo(a);
    foo(1);
}

但是這樣在foo中就無法修改a的值,不夠靈活落剪,于是我們加入右值引用的foo實現(xiàn):

void bar(const int& a)
{
    cout << a << endl;
}

void foo(int&& a)
{
    cout << "rvalue" << endl;
    bar(a);
}

void foo(int& a)
{
    cout << "lvalue" << endl;
    bar(a);
}

int main()
{
    int a = 2;
    foo(a);
    foo(1);
}

看似完美解決了問題尿庐,但是這是單個參數(shù)的情況,如果是2個參數(shù),那么就要重載4個函數(shù)暮胧,3個參數(shù)就要重載8個函數(shù)……
于是有了萬能引用:

void bar(const int& a)
{
    cout << a << endl;
}

template<typename T>
void foo(T&& a)
{
    bar(a);
}

int main()
{
    int a = 2;
    foo(a);
    foo(1);
}

似乎一切正常,但是僅僅這樣是不夠的钞翔,如果我們重載一下bar,就會發(fā)現(xiàn)哮笆,foo把左值和右值引用都轉(zhuǎn)化成了左值:

void bar(int& a)
{
    cout << "lvalue" << endl;
}

void bar(int&& a)
{
    cout << "rvalue" << endl;
}

template<typename T>
void foo(T&& a)
{
    // a here is always a left reference
    bar(a);
}

int main()
{
    int a = 2;
    foo(a);
    foo(1);
}

由于自動類型推導(dǎo)(常數(shù)1被推導(dǎo)為int型)汰扭,這將輸出兩個lvalue,顯然不符合我們的預(yù)期项阴,此時笆包,搭配std::forward才能正確地實現(xiàn)完美轉(zhuǎn)發(fā):

void bar(int& a)
{
    cout << "lvalue" << endl;
}

void bar(int&& a)
{
    cout << "rvalue" << endl;
}

template<typename T>
void foo(T&& a)
{
    bar(forward<T>(a));
}

int main()
{
    int a = 2;
    foo(a);
    foo(1);
}

這將輸出:

lvalue
rvalue

引用折疊

上述內(nèi)容還涉及一個問題,引用的引用如何定義歉胶?
對于如下萬能引用:

template<typename T>
void foo(T&& a);

當T的類型為一個int&時巴粪,a的類型事實上是int& &&,也就是一個左值引用的右值引用衡创,但是C++不允許類似int& &&這樣的類型聲明晶通,它要么是int&要么是int&&,于是就有了引用折疊:

int& && -> int&
int& & -> int&
int&& & -> int&
int&& && -> int&&

可以看到左值引用具有傳染性一也,類似一個或門喉脖,僅有右值引用的右值引用折疊結(jié)果為右值引用,這也是std::forward的實現(xiàn)原理:

template<typename T>
T&& forward(typename std::remove_reference<T>::type& t) noexcept {
    return static_cast<T&&>(t);
}

template<typename T>
T&& forward(typename std::remove_reference<T>::type&& t) noexcept {
    static_assert(!std::is_lvalue_reference<T>::value, "Invalid argument: cannot forward an lvalue as an rvalue.");
    return static_cast<T&&>(t);
}

可以看到舆蝴,std::forward通過std::remove_reference去除了t的引用限定符,也就是不管類型T加了多少引用限定符层皱,都回歸到基本類型赠潦,比如一個int& &&,經(jīng)過std::remove_reference她奥,會得到一個int類型,通過重載绷跑,自動調(diào)用左值或右值版本携茂,并在最后用static_cast<T&&>返回一個t的右值引用弛姜,又由于引用折疊,t如果是一個左值引用扇售,比如int&膝藕,那么返回的就是int& &&咐扭,最終還是一個int&,同理蝗肪,int&& &&返回的就是一個int&&薛闪。

相關(guān)話題:std::move

設(shè)想如下代碼:

void bar(int& a)
{
    cout << "lvalue" << endl;
}

void bar(int&& a)
{
    cout << "rvalue" << endl;
}

template<typename T>
void foo(T&& a)
{
    bar(forward<T>(a));
}

void test(int& a)
{
    cout << "ltest" << endl;
    foo(a);
}

void test(int&& a)
{
    cout << "rtest" << endl;
    foo(a);
}

int main()
{
    int a = 2;
    test(a);
    test(2);
}

在執(zhí)行test函數(shù)時,可以正常重載對應(yīng)類型的函數(shù)昙篙,但是test調(diào)用foo時诱咏,由于自動類型推導(dǎo),兩次都是lvalue袋狞,輸出為:

ltest
lvalue
rtest
lvalue

使用std::move,可以將變量顯式轉(zhuǎn)換為右值引用:

void bar(int& a)
{
    cout << "lvalue" << endl;
}

void bar(int&& a)
{
    cout << "rvalue" << endl;
}

template<typename T>
void foo(T&& a)
{
    bar(forward<T>(a));
}

void test(int& a)
{
    cout << "ltest" << endl;
    foo(a);
}

void test(int&& a)
{
    cout << "rtest" << endl;
    foo(move(a));   // same as std::forward<int&&>(a) here
}

int main()
{
    int a = 2;
    test(a);
    test(2);
}

這將輸出:

ltest
lvalue
rtest
rvalue

如上場景使用move實現(xiàn)了類似forward的功能秧荆,兩者又是有區(qū)別的埃仪,forward只是保持來源的引用類型不變陕赃,而move可以將左值轉(zhuǎn)變?yōu)橛抑担热缦胍{(diào)用移動構(gòu)造函數(shù)而非拷貝構(gòu)造函數(shù)來節(jié)約內(nèi)存時么库,就可以通過move實現(xiàn)诉儒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市泛释,隨后出現(xiàn)的幾起案子温算,更是在濱河造成了極大的恐慌,老刑警劉巖注竿,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巩割,死亡現(xiàn)場離奇詭異,居然都是意外死亡愈犹,警方通過查閱死者的電腦和手機蒲祈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扬卷,“玉大人酸钦,你說我怎么就攤上這事⊥搅担” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵亿乳,是天一觀的道長径筏。 經(jīng)常有香客問我,道長聊训,這世上最難降的妖魔是什么恢氯? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮勋磕,結(jié)果婚禮上指黎,老公的妹妹穿的比我還像新娘。我一直安慰自己醋安,他們只是感情好,可當我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布亲怠。 她就那樣靜靜地躺著团秽,像睡著了一般叭首。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上焙格,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天眷唉,我揣著相機與錄音囤官,去河邊找鬼蛤虐。 笑死,一個胖子當著我的面吹牛驳庭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捏检,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼不皆,長吁一口氣:“原來是場噩夢啊……” “哼霹娄!你這毒婦竟也來了鲫骗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤枕磁,失蹤者是張志新(化名)和其女友劉穎术吝,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體排苍,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡淘衙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年彤守,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片具垫。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡做修,死狀恐怖抡草,靈堂內(nèi)的尸體忽然破棺而出蔗坯,到底是詐尸還是另有隱情,我是刑警寧澤宾濒,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布绘梦,位于F島的核電站,受9級特大地震影響卸奉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜凝颇,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一疹鳄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧垫蛆,春花似錦腺怯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至佛猛,卻和暖如春坠狡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背逃沿。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留边臼,地道東北人。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓岭接,卻偏偏與公主長得像臼予,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子窄锅,可洞房花燭夜當晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容