C++的基於範圍的循環是C++11出現的新特性,很方便,必定程度上替代了使用迭代器的for循環用法。不過基於範圍的for循環有一個隱藏的陷阱,若是不注意可能會出現嚴重的內存錯誤。html
看下面這個代碼:ios
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行,改成如下的樣子:express
for (auto ch : MyClass().getText()) { cout << ch; }
結果什麼都不會輸出,程序直接退出。要理解爲何會出現這種行爲,要先知道基於範圍的for循環是怎麼定義的。oop
在C++11標準中,它有如下的格式spa
attr(optional) for ( range_declaration : range_expression ) loop_statement
其中attr是可選的,range_declaration部分至關於咱們代碼中的 "auto ch",range_expression部分至關於 "MyClass().getText()",loop_statement就是 "{ cout << ch; }"code
標準規定,上面的循環表達式應當等價於htm
{ 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的生存期。blog
看了給予範圍的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循環很是方便,甚至能夠遍歷臨時對象,在平常中也常常使用到。可是要注意的是,若是要遍歷臨時對象的話,須要遍歷的臨時對象必須是右值表達式,並且也要注意表達式中間產生的其餘臨時對象是在循環開始前就會被銷燬的,只有表達式返回的最後的臨時對象纔會被「存」起來。