對象複製問題 && 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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...