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

来源:https://www.cnblogs.com/zhao-zongsheng/archive/2018/03/26/8653108.html
-Advertisement-
Play Games

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


轉載請保留以下聲明
  作者:趙宗晟
  出處: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迴圈非常方便,甚至可以遍歷臨時對象,在日常中也經常使用到。但是要註意的是,如果要遍歷臨時對象的話,需要遍歷的臨時對象必須是右值表達式,而且也要註意表達式中間產生的其他臨時對象是在迴圈開始前就會被銷毀的,只有表達式返回的最後的臨時對象才會被“存”起來。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.應用參數,在web.xml配置,所有Servlet共用 服務端獲取配置的數據,實現Servlet介面 2.私有參數,在web.xml配置 服務端獲取參數數據,實現Servlet介面 3."會話“參數 ClassA設置參數值 ClassB獲取參數值 4.“行為”參數,在HTML、Jsp等前端頁面編 ...
  • 1,補充一點列表傳參需要註意的地方:列表傳參,是傳引用 執行結果: 2,我們可以在函數裡面對傳遞的列表參數,做一個拷貝,就不會是傳引用了 執行後 集合:沒有順序的概念,不能進行索引或者切片操作 1、創建集合. set():可變的 不可變的frozenset() 2,集合add與update操作 up ...
  • 近一段時間在圖像演算法以及音頻演算法之間來回游走。 經常有一些需求,需要將音頻進行採樣轉碼處理。 現有的知名開源庫,諸如: webrtc , sox等, 代碼閱讀起來實在鬧心。 而音頻重採樣其實也就是插值演算法。 與圖像方面的插值演算法沒有太大的區別。 基於雙線性插值的思路。 博主簡單實現一個簡潔的重採樣算 ...
  • 第一步:安裝python 1 首先進入網站下載:點擊打開鏈接(或自己輸入網址https://www.python.org/downloads/),進入之後如下圖,選擇圖中紅色圈中區域進行下載 2 雙擊exe文件進行安裝,如下圖,並按照圈中區域進行設置,切記要勾選打鉤的框,然後再點擊Customize ...
  • C/C++,dynamic_cast, static_cast, const_cast, 大端,小端 ...
  • 結果: 0 jack 1 jill 2 jease 3 feank dtype: object Index([' Column A', ' Column B'], dtype='object') ['jack ' 'jill' 'jease ' 'feank'] [' jack' 'jill' ' ...
  • 記錄背景:最近由於想實現GMIItoRGMII的功能,因此需要調用ODDR原語。 ODDR:Dedicated Dual Data Rate (DDR) Output Register 通過ODDR把兩路單端的數據合併到一路上輸出,上下沿同時輸出數據,上沿輸出a路下沿輸出b路;如果兩路輸入信號一路恆 ...
  • 字元串的常用操作包括但不限於以下操作: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...