C++基於範圍迴圈(range-based for loop)的陷阱

來源:互聯網
上載者:User

標籤:分析   base   loop   style   迴圈語句   att   pac   舉例   text   

C++的基於範圍的迴圈是C++11出現的新特性,很方便,一定程度上替代了使用迭代器的for迴圈用法。不過基於範圍的for迴圈有一個隱藏的陷阱,如果不注意可能會出現嚴重的記憶體錯誤。

舉例說明

看下面這個代碼:

 1 #include <iostream> 2 #include <string> 3  4 using namespace std; 5  6 struct MyClass 7 { 8     string text = "MyClass"; 9 10     string& getText()11     {12         return text;13     }14 };15 16 int main()17 {18     for (auto ch : MyClass().text)19     {20         cout << ch;21     }22     cout << endl;23 }

這個代碼很簡單,輸出結果就是 "MyClass"。但如果稍微修改第18行,改為以下的樣子:

    for (auto ch : MyClass().getText())    {        cout << ch;    }

結果什麼都不會輸出,程式直接退出。要理解為什麼會出現這種行為,要Crowdsourced Security Testing道基於範圍的for迴圈是怎麼定義的。

基於範圍的for迴圈定義

在C++11標準中,它有以下的格式

attr(optional) for ( range_declaration : range_expression ) loop_statement

其中attr是可選的,range_declaration部分相當於我們代碼中的 "auto ch",range_expression部分相當於 "MyClass().getText()",loop_statement就是 "{ cout << ch; }"

標準規定,上面的迴圈運算式應當等價於

{    auto && __range = range_expression;    for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) {        range_declaration = *__begin;        loop_statement    }}

其中begin_expr和end_expr由range_expression的類型來決定。

這裡面值得注意的是,第一行聲明的__range類型是 "auto &&",所以如果range_expression是右值的臨時對象,則__range可以延長range_expression的生存期。

問題分析

看了給予範圍的for迴圈的定義之後,前面例子中的問題出現的原因就很清楚了。

原始的例子中,range_expression是 "MyClass().text",MyClass()是臨時對象,同時 "MyClass()" 這個運算式是右值。所以,"MyClass().text" 這個運算式也是右值,"MyClass().text" 這個對象是臨時對象中的一部分。所以,在 "auto && __range = range_expression;" 這個語句中,auto會被推導為 "std::string"。初始化右值引用為臨時對象的一部分時,可以延長整個臨時對象的生存期,在引用被銷毀時臨時對象才會被銷毀。所以for迴圈可以正常執行。

但是在修改過後,range_expression是 "MyClass().getText()"。同樣地,MyClass()是臨時對象,"MyClass()" 這個運算式是右值。但是 "getText()" 的傳回型別為 "string&",所以,"MyClass().getText()" 這個運算式是左值。所以,在 "auto && __range = range_expression;" 這個語句中,auto會被推導為 "string &",語句等價於 "string & __range = range_expression;" 。雖然"MyClass().getText()" 這個對象是臨時對象中的一部分,但是在初始化非const的左值引用時,不會延長臨時對象的生存期,所以在這個初始化語句結束的同時MyClass()這個臨時對象就被銷毀了,__range成為了野引用,所以後面的迴圈語句可能會出現記憶體錯誤。

總結

基於範圍的for迴圈非常方便,甚至可以遍曆臨時對象,在日常中也經常使用到。但是要注意的是,如果要遍曆臨時對象的話,需要遍曆的臨時對象必須是右值運算式,而且也要注意運算式中間產生的其他臨時對象是在迴圈開始前就會被銷毀的,只有運算式返回的最後的臨時對象才會被“存”起來。

C++基於範圍迴圈(range-based for loop)的陷阱

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.