對象複製問題 && lvalue-rvalue

来源:http://www.cnblogs.com/yunqie/archive/2016/10/11/5947893.html
-Advertisement-
Play Games

按值傳遞實參到函數和函數返回臨時變數的副本,函數的效率對執行性能來說至關重要 如果避免這樣的複製操作,則執行時間可能會大大縮短。 ...


按值傳遞實參到函數和函數返回臨時變數的副本,函數的效率對執行性能來說至關重要

如果避免這樣的複製操作,則執行時間可能會大大縮短。

class CMessage
{
private:
    char * m_pMessage;

public:
    void showIt()const
    {
        cout << m_pMessage << endl;
    }
    //構造函數
    CMessage(const char* text = "Default message")
    {
        cout << " 構造函數" << endl;

        size_t length{ strlen(text) + 1 };
        m_pMessage = new char[length + 1];
        strcpy_s(m_pMessage, length + 1, text);
    }
    //複製構造函數
    CMessage(const CMessage & aMess)
    {
        cout << "複製構造函數" << endl;
        size_t len{ strlen(aMess.m_pMessage) + 1 };
        this->m_pMessage = new char[len];
        strcpy_s(m_pMessage, len, aMess.m_pMessage);
    }
    //重載賦值運算符
    CMessage & operator=(const CMessage & aMess)
    {
        cout << "重載賦值運算符函數" << endl;
        if (this != &aMess)
        {
            delete[]m_pMessage;
            size_t length{ strlen(aMess.m_pMessage) + 1 };
            m_pMessage = new char[length];
            strcpy_s(this->m_pMessage, length, aMess.m_pMessage);
        }
        return *this;
    }

    CMessage operator+(const CMessage & aMess)
    {
        cout <<"重載加法運算符函數" << endl;
        size_t len{strlen(m_pMessage)+strlen(aMess.m_pMessage)+1};
        CMessage message;
        message.m_pMessage = new char[len];

        strcpy_s(message.m_pMessage,len,m_pMessage);
        strcat_s(message.m_pMessage,len,aMess.m_pMessage);

        return message;
    }

    //析構函數
    ~CMessage()
    {
        cout << " 析構函數" << endl;
        delete[]m_pMessage;
    }
};
int main()
{
    CMessage motto1{ "Amiss is " };
    CMessage motto2{"as good as a mile"};
    CMessage motto3;

    motto3 = motto1 + motto2;

    motto3.showIt();
}

運行結果如下:

 構造函數            //motto1調用
 構造函數            //motto2調用
 構造函數            //motto3調用
重載加法運算符函數        
 構造函數            //operator+()中message對象調用
複製構造函數           //返回時對message對象的複製,生成message的臨時副本
 析構函數            //message調用,銷毀臨時對象
重載賦值運算符函數        //motto3調用operator=()
 析構函數              //message的臨時副本調用
Amiss is as good as a mile
 析構函數
 析構函數
 析構函數

----------------------------------------------------------------------------------------------------------------------------------------------------------------

改進方法:應用rvalue引用形參

當源對象是一個臨時對象,在複製操之後立即就被銷毀時,複製的替代方案是偷用由 m_pMessage 成員指向的臨時對象的記憶體,並傳送到目標對象。

如果這麼做,那麼不需要為目標對象分配更多的記憶體,不需要複製對象,也不需要釋放源對象擁有的記憶體。

在操作完成以後將立即銷毀源對象,因此這麼做沒有風險,只是加快了執行速度。

實現此技術的關鍵是檢測複製操作中何時是一個 rvalue。

  CMessage(const CMessage & aMess)
  {
      cout << "複製構造函數" << endl;
      size_t len{ strlen(aMess.m_pMessage) + 1 };
      this->m_pMessage = new char[len];
      strcpy_s(m_pMessage, len, aMess.m_pMessage);
   }

  CMessage(CMessage && aMess)
   {
      cout << "" << endl;
      m_pMessage = aMess.m_pMessage;
      aMess.m_pMessage = nullptr;      //必須要這麼做,防止刪除原指向的記憶體
    }

我們知道用對象初始化當前對象、返回臨時對象都會調用複製構造函數。

motto3 = motto1 + motto2;調用重載加法運算符函數後會產生臨時變數 message。

而對臨時變數的複製產生需要的臨時副本會增加運行時間。

所以,臨時變數的副本可以直接“偷用”源臨時變數對象成員指向的記憶體。通過以上兩個函數比較可知,lvalue引用形參多了複製的操作。

難道要 lvalue引用形參的形式沒有用了嗎?

若: motto3=motto1 時,可知必須用 lvalue 引用形參的形式。如果 rvalue 可用,將會使兩個對象同時指向一塊記憶體。

所以, rvalue 針對 臨時變數。

 

可以像下麵這樣額外創建 operator=()函數的重載

  CMessage & operator=(CMessage && aMess)
   {
      cout <<"Move assignment operator function called." << endl;
      delete[]m_pMessage;
      m_pMessage = aMess.m_pMessage;
      aMess.m_pMessage = nullptr;       //必須要這麼做,防止刪除原指向的記憶體
      return *this;
   }

  CMessage & operator=(const CMessage & aMess)
  {
     cout << "重載賦值運算符函數" << endl;
     if (this != &aMess)
     {   delete[]m_pMessage;
        size_t length{ strlen(aMess.m_pMessage) + 1 };
          m_pMessage = new char[length];
        strcpy_s(this->m_pMessage, length, aMess.m_pMessage);
     }
     return *this;
  }

觀察這兩個 lvalue、rvalue引用形參重載賦值運算符函數的區別:

motto3 = motto1 + motto2;調用重載加法運算符函數後會產生臨時變數 message。在函數返回時又調用 rvalue引用形參類型的複製構造函數,

產生臨時對象 message 的臨時副本。

之後調用重載賦值運算符函數,由於此時仍是臨時對象的副本,

所以,仍可以採用“ 偷換 ”源臨時變數對象成員指向的記憶體。而避免賦值函數對對象成員的複製。

臨時對象由編譯器生成,使用之後會自動調用析構函數釋放。

所以此處需要我們通過觀察代碼運行,自己來理解。

 

  CMessage operator+(const CMessage & aMess)
   {
    cout <<"重載加法運算符函數" << endl;
    size_t len{strlen(m_pMessage)+strlen(aMess.m_pMessage)+1};
    CMessage message;
    message.m_pMessage = new char[len];

    strcpy_s(message.m_pMessage,len,m_pMessage);
    strcat_s(message.m_pMessage,len,aMess.m_pMessage);

    return message;
   }

不知道你有沒想過喲,為什麼上面函數沒有返回引用,引用可以避免不必要的複製,不是很方便嗎?

添加引用 CMessage operator+(const CMessage & aMess)

運行結果:

   構造函數
   構造函數
   構造函數
  重載加法運算符函數
   構造函數
   析構函數
  重載賦值運算符函數
請按任意鍵繼續. . .

發現程式崩潰,運行到重載賦值運算符函數就不能繼續運行了。

Why?

如果被返回的對象是被調用函數中的局部變數,則不應按引用方式返回它。

因為,在被調用函數執行完畢時,局部對象將調用其 析構函數。

 

如果函數返回一個沒有公有複製構造函數的類(如 ostream 類)的對象,它必須返回指向對象的引用。

如果在類中定義了 operator=()成員函數和複製構造函數時,將形參定義為非常量 rvalue 引用,則需要確保也定義了具有 const lvalue引用形參的標準版本。

編譯器會提供它們的預設版本,逐一成員的進行複製。


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

-Advertisement-
Play Games
更多相關文章
  • 一、博客系統進度回顧 目前已經完成了,前臺展示,以及後臺發佈的功能,最近都在做這個,其實我在國慶的時候就可以弄完的,但是每天自己弄,突然最後國慶2天,連電腦都不想碰,所以就一直拖著,上一篇寫了前端實現用到的一些WebUI框架以及一些系統中用到的js插件,其實寫了這麼久,還是第一次,有人留言,不要爛尾 ...
  • 這兩天看一個要離職同事交接的代碼,看到一個淺拷貝的方法感覺挺好,在這裡記錄一下。 一、方法體 是一個靜態方法CopyHelper,包含以下三個部分 1、給PropertyInfo[]類型加個擴展的方法,方便進行查詢是否存在相同類型,相同名稱的欄位。 2、淺拷貝的主體方法,即調用上面的方法基礎上,如果 ...
  • 最前面的話:Smobiler是一個在VS環境中使用.Net語言來開發APP的開發平臺,也許比Xamarin更方便 ...
  • 上一章講到關於C#語法的基礎部分。瞭解相關的基礎部分之後我們就要去瞭解一下C#是什麼樣子訪問數庫的。C#把訪問資料庫這一部分的知識點叫作ADO.NET。即是JAVA常常講到的JDBC這一部分的知識點了。筆者根據使用資料庫方式的不同又分為有線連接和無線連接(關於有線和無線的叫法是筆者個人定義的。因為看 ...
  • C# 中的datagridview是一個非常有用且強大的控制項,可以用來綁定資料庫、綁定LIST類型的變數等等。 這裡我們說一說綁定List類型並實時更新datagridview的情況。實時更新,指的是在我操作BindingList時(刪除/Add)等操作的時候,datagridview也會發生相應變 ...
  • ...
  • 第 3章 異常及錯誤處理 健壯的程式來自於正確的錯誤處理。 相信我,總會有意外的…… Delphi 高手突破 正如同現實生活中我們不可能事事如意,你所寫的代碼也不可能每一行都能得到正確的執行。生活中遇到不如意的事情,處理好了,雨過天晴;處理不好,情況會越變越糟,甚至一發而不可收拾,後果難料。程式設計 ...
  • 題目: Given a roman numeral, convert it to an integer.Input is guaranteed to be within the range from 1 to 3999. 官方難度: Easy 翻譯: 給定一個羅馬數字,翻譯成一個阿拉伯數字的整數形式 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...