C++基於范圍循環(range-based for loop)的陷阱


轉載請保留以下聲明
  作者: 趙宗晟
  出處: http://www.cnblogs.com/zhao-zongsheng/p/8653108.html

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;
    }

結果什么都不會輸出,程序直接退出。要理解為什么會出現這種行為,要先知道基於范圍的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循環非常方便,甚至可以遍歷臨時對象,在日常中也經常使用到。但是要注意的是,如果要遍歷臨時對象的話,需要遍歷的臨時對象必須是右值表達式,而且也要注意表達式中間產生的其他臨時對象是在循環開始前就會被銷毀的,只有表達式返回的最后的臨時對象才會被“存”起來。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM