C++類對象的隱式類型轉換和編譯器返回值優化

文章目錄

  • 前言
  • 1. 隱式類型轉換
    • 1.1 單參數的隱式類型轉換
    • 1.2 多參數的隱式類型轉換
    • 1.3 explicit關鍵字
  • 2. 編譯器的優化
    • 2.1 普通構造優化
    • 2.2 函數傳參優化
    • 2.3 函數返回優化

前言

在類與對象的學習過程中,一定會對隱式類型轉換這個詞不陌生。對于內置類型而言,相似的類型會支持隱式類型轉換,例如int a = 3.1;在這篇文章我們細細談談類對象中的隱式類型轉換

  1. 類對象的隱式類型轉換
  2. 編譯器的優化

注意:這里的討論,沒有考慮右值

1. 隱式類型轉換

關于隱式類型轉換產生臨時變量,我在類型轉換細節中有談到,大家可以先閱讀一下。

1.1 單參數的隱式類型轉換

C++11之前,C++98僅僅支持單參數的隱式類型轉換,當然這種轉換也帶來了很多的便利!同時也有潛在危險,需要合理看待!

為了方便測試,我們在VS2022下,給出以下的A類:

#include<iostream>
using namespace std;
class A
{
public:A(int a = 1):_a(a){cout << "A(int a = 1)" << endl;}~A(){cout << "~A()" << endl;}A(const A& a){cout << "A(const A& a)" << endl;}A& operator=(const A& a){cout << "A& operator=(const A& a)" << endl;return *this;}//暫時不考慮移動構造和移動賦值int _a;
};

在大多數時候,我們會寫出這樣的代碼, 例1.1.1:

int main()
{A a = 1; //直接以1來賦值這個a類對象return 0;
}

這個過程會發生什么呢?

  1. 編譯器會先利用1來構造一個A類的tmp對象

  2. 調用a的對象的拷貝構造函數

(但似乎這樣的代價也太大了,所以編譯器會做出優化,我們稍后再談)

當我們在使用STL的時候,也常常會發生這種隱式類型的轉換,例如:

#include<iostream>
#include<string>
#include<vector>
using namespace std;int main()
{vector<string> v;v.push_back("this is a string?");return 0;
}

我們的vector存放的是string類對象,而我們傳入的卻是一個char*類型(字符串常量類型都被解釋為了const char *)。在不考慮其它因素外,這個時候就應該會發生隱式類型的轉換:將char*類型構造一個string對象,再調用push_back函數

這就是單參數的隱式類型轉換

1.2 多參數的隱式類型轉換

C++11支持了列表初始化
在這里插入圖片描述
(C++11我們會在后面的專題談到)

也對類的多參數的情況進行了升級!支持了多參數的隱式類型轉換。給出下面一個例子:

#include<iostream>
using namespace std;
class A
{
public:A(int a = 1, int b = 2):_a(a),_b(b){cout << "A(int a = 1, int b = 2)" << endl;}~A(){cout << "~A()" << endl;}A(const A& a){cout << "A(const A& a)" << endl;}A& operator=(const A& a){cout << "A& operator=(const A& a)" << endl;return *this;}int _a;int _b;
};

那么支持了多參數的隱式類型轉換之后,我們可以這么寫
例1.2.1:

int main()
{A a = {1, 3}; //支持的return 0;
}

同樣地,我們應該了解到這個語句干了什么?

  1. 編譯器會先利用{1, 3}來構造一個A類的tmp對象

  2. 調用a的對象的拷貝構造函數

1.3 explicit關鍵字

有些時候,我們其實并不想構造函數支持這種隱式類型轉換,我們就可以采用關鍵字explicit來對構造函數進行聲明!

語法如下:

class A
{
public:explicit A(int a = 1, int b = 2):_a(a),_b(b){cout << "A(int a = 1, int b = 2)" << endl;}~A(){cout << "~A()" << endl;}int _a;int _b;
};int main()
{//關于這樣的代碼就無法通過編譯了!//A a = {1, 3}; //不支持了return 0;
}

但是這樣真的很方便……所以我們還可以采用另外一個形式,匿名對象

例1.3.1

int main()
{A a = A{ 1, 3 }; //語句一//A a = A({ 1, 3 }); //語句二return 0;
}

說明:

  1. 語句一可以在支持隱式類型轉換的情況下使用。本質和隱式類型轉換類似,都是構造一個tmp類對象。
  2. 語句二使用的前提是這個A類支持initializer_listA類構造函數。本質上是{ }調用了initializer_list的構造函數,是一個initializer_listtmp對象,然后再初始化A類。所以,當你的A類不支持這樣的一個構造函數,就無法成功初始化了!

是否需要驗證呢?

#include<iostream>
using namespace std;
class A
{
public:explicit A(int a = 1, int b = 2):_a(a), _b(b){cout << "A(int a = 1, int b = 2)" << endl;}~A(){cout << "~A()" << endl;}A(initializer_list<int> il) //支持列表初始化的構造函數{cout << "A(initializer_list<int> il)" << endl;}int _a;int _b;
};int main()
{A a = A({ 1, 3 }); //注意不要寫成這樣return 0;
}

在這里插入圖片描述

這樣的調用,不知道是否有說服力呢?

2. 編譯器的優化

在上面的大多數例子中,我并沒有驗證那些我們看起來的步驟。因為:編譯器是會對同一行的連續構造采取優化措施的

現在,在來考慮這個類,和幾條語句:

2.1 普通構造優化

例2.1.1:

class A
{
public:A(int a = 1, int b = 2):_a(a),_b(b){cout << "A(int a = 1, int b = 2)  " << _a << endl; //為了區別每一個構造,這里多給了一個打印}~A(){}A(const A& a){cout << "A(const A& a)" << endl;}A& operator=(const A& a){cout << "A& operator=(const A& a)" << endl;return *this;}int _a;int _b;
};int main()
{A a0(-1, -1);cout << " ------------------ " << endl;A a1 = a0; //語句一A a2 = { 0, 0 }; //語句二A a3 = A{ 1, 1 }; //語句三return 0;
}

來看運行結果:
在這里插入圖片描述
(單參數的也是這樣的結果)
在此之前,我們并沒有給出實際的運行結果,因為編譯器會為我們做出優化:

  • 語句一沒有優化。a0本身就是一個存在的對象!
  • 語句二、三進行了優化,本來我們應該是先普通構造再拷貝構造,但編譯器為我們直接構造

2.2 函數傳參優化

同樣直接給出示例:

class A
{
public:A(int a = 1, int b = 2):_a(a),_b(b){cout << "A(int a = 1, int b = 2)  " << _a << endl; //為了區別每一個構造,這里多給了一個打印}~A(){}A(const A& a){cout << "A(const A& a)" << endl;}A& operator=(const A& a){cout << "A& operator=(const A& a)" << endl;return *this;}int _a;int _b;
};void func(A a) //注意這里并沒有傳引用 -- 傳引用就不會進行拷貝構造了
{//……
}int main()
{A a0(-1, -1);cout << " ------------------ " << endl;func(a0); //語句一func({ 2, 2 }); //語句二func(A{ 3, 3 }); //語句三return 0;
}

在這里插入圖片描述

同樣發生了優化!

2.3 函數返回優化

這里的函數返回值優化又有所不同,返回值優化又被稱為:RVO(Return Value Optimization)

給出示例:

class A
{
public:A(int a = 1, int b = 2):_a(a),_b(b){cout << "A(int a = 1, int b = 2)  " << _a << endl; //為了區別每一個構造,這里多給了一個打印}~A(){}A(const A& a){cout << "A(const A& a)" << endl;}A& operator=(const A& a){cout << "A& operator=(const A& a)" << endl;return *this;}int _a;int _b;
};A func()
{A tmp(5,5);//……return tmp;
}int main()
{A a0(-1, -1);cout << " ------------------ " << endl;a0 = func(); //語句一A a1 = func(); //語句二return 0;
}

我們在func中創建一個臨時變量tmp,想讓這個tmp完成一些業務,然后返回這個臨時變量。來看運行結果:
在這里插入圖片描述
語句一:創建一個tmp對象,然后調用一個賦值運算符重載,十分合理的。但是看到語句二直接就完成了構造!。沒有在func()中調用tmp的構造函數?還是沒有調用a1的構造函數?

  1. 首先分析:A a1 = func();。首先函數返回的時候是會將返回值拷貝到一個tmp對象中的,然后再通過這個tmp對象返回給外面的接收變量,這里本身就有兩個拷貝構造,編譯器發生優化是很情理之中的!
  2. 同時函數func中又定義了一個變量,這個變量也會調用一個構造函數的。
  3. 可是結果告訴我們整個的調用只調用了一次構造函數

沒錯,這就是編譯器的RVO

RVO編譯器優化技術。它可以減少函數返回時創建臨時對象的次數,從而提高程序的運行效率。RVO主要針對未命名的臨時對象,消除了函數返回時創建的臨時對象,避免了不必要的拷貝構造函數調用。

來看這樣一張圖片:
在這里插入圖片描述

發現了嗎?tmp這個對象被處理成為了一個指針!這個指針指向的對象就是a1對象。我們通過對tmp的操作,在編譯器看來就是對a1進行操作。所以上述情況下只會調用一次拷貝構造函數

在有些時候,這樣采用RVO的代碼效率不差同時也更好維護!大家可以自己做性能測試!

RVO并不總是適用,存在一些限制條件,例如:

  • 函數拋出異常時,RVO可能不會進行。

  • 函數可能返回具有不同變量名的對象時,RVO無法進行。

  • 函數有多個出口時,RVO可能不會進行。

希望這篇文章能夠幫助到你!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/82009.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/82009.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/82009.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

領麥微紅外溫度傳感器,搖奶器測溫應用

在育兒領域&#xff0c;精準控制奶液溫度是守護寶寶健康的重要環節。領麥微作為MEMS傳感器領域的創新先鋒&#xff0c;通過其紅外測溫傳感器的非接觸式測量、高精度測溫、實時反饋以及智能溫控節能等核心優勢&#xff0c;為搖奶器注入了全新的智能化解決方案。這一技術不僅提升…

第十一屆藍橋杯 2020 C/C++組 蛇形填數

目錄 題目&#xff1a; 題目描述: 題目鏈接&#xff1a; 思路&#xff1a; 思路詳解&#xff1a; 代碼&#xff1a; 代碼詳解&#xff1a; 題目&#xff1a; 題目描述: 題目鏈接&#xff1a; 蛇形填數 - 藍橋云課 思路&#xff1a; 思路詳解&#xff1a; 看圖找規律…

如何檢查 Watchtower 是否正常工作及更新未生效的排查方法【日常排錯】

文章目錄 前言一、驗證 Watchtower 是否正在運行1. 檢查 Watchtower 容器狀態2. 查看 Watchtower 日志 二、檢查5分鐘間隔設置是否正確1. 確認啟動命令2. 驗證環境變量 三、排查更新未生效的原因1. 檢查是否有鏡像更新2. 檢查容器標簽3. 檢查監控范圍 四、測試 Watchtower 功能…

寶塔面板,刪除項目后還能通過域名進行訪問

場景&#xff1a;在阿里云寶塔面板中&#xff0c;刪除了之前建立的html項目&#xff0c;通過之前綁定的域名還是可以訪問&#xff0c;又把項目的目錄文件刪除&#xff0c;發現還是不行 又清理了瀏覽器緩存&#xff0c;但還是有這個問題通過該域名重新創建一個html項目&#xff…

多層PCB SMT貼裝全流程指南:從物料準備到回流焊工藝控制

在電子制造領域&#xff0c;多層PCB板元器件貼片是一項重要的技術操作。本文將詳細介紹多層PCB板元器件貼片的操作流程和注意事項&#xff0c;幫助您更好地理解和掌握這項技術。 一、準備階段 在進行多層PCB板元器件貼片操作前&#xff0c;需要做好以下準備工作&#xff1a; 1.…

PAT(最近)

1022 D進制的AB - PAT (Basic Level) Practice &#xff08;中文&#xff09; 加減位置調換 本來以為就是簡單的 十進制轉換為一個長的字符串 沒想到在那個拼接字符串的時候 只需要簡單的 加減位置調換就可以 避免使用麻煩的翻轉函數 import java.util.Scanner; public clas…

【Harbor v2.13.0 詳細安裝步驟 安裝證書啟用 HTTPS】

Harbor v2.13.0 詳細安裝步驟&#xff08;啟用 HTTPS&#xff09; 1. 環境準備 系統要求&#xff1a;至少 4GB 內存&#xff0c;100GB 磁盤空間。 已安裝組件&#xff1a; Docker&#xff08;版本 ≥ 20.10&#xff09;Docker Compose&#xff08;版本 ≥ v2.0&#xff09; 域…

以pytest_addoption 為例,講解pytest框架中鉤子函數的應用

鉤子函數&#xff08;Hook Function&#xff09;的概念 鉤子函數&#xff08;Hook Function&#xff09;是軟件框架中預定義的回調接口&#xff0c;允許開發者在程序執行的特定階段插入自定義邏輯&#xff0c;以擴展或修改框架的默認行為。在 pytest 中&#xff0c;鉤子函數覆…

合并兩個有序鏈表 - 簡單

************* C topic: 21. 合并兩個有序鏈表 - 力扣&#xff08;LeetCode&#xff09; ************* Give the topic an inspection. Hi, guys, how is your holiday break? I went to 黃山 in the past few days. The mount Huang is really beautiful. 天都峰 is real…

13.Spring boot中使用Actuator 監控

13.Spring boot中使用Actuator 監控 Spring Boot Actuator 是 Spring Boot 提供的一個強大的監控和管理工具&#xff0c;它通過暴露各種端點&#xff08;Endpoints&#xff09;來提供應用程序的運行時信息。這些端點可以幫助開發者和管理員監控應用程序的健康狀況、性能指標、…

Python+Scrapy跨境電商爬蟲實戰:從亞馬遜/沃爾瑪數據采集到反爬攻克(附Pangolin API高效方案)

從零實戰到反爬攻克&#xff0c;揭秘跨境數據抓取全流程與Pangolin Scrape API終極方案 在當今數據驅動的跨境電商時代&#xff0c;誰掌握了優質的市場數據&#xff0c;誰就掌握了成功的關鍵。隨著全球電商市場規模持續擴大&#xff08;據Statista最新報告顯示&#xff0c;2025…

0基礎學習鴻蒙開發-HarmonyOS4

一、初識 1. 開發工具 官網 開發-HarmonyOS NEXT鴻蒙應用開發平臺-華為開發者聯盟 2. ArkTS 二、TypeScript 基本語法 1.變量聲明 2. 條件控制 注意 在TypeScrips中 空字符串數字0、null、undefined 都坡認為是false 其它值則為true if (num) {// num 非空執行 } 3. 循環迭…

深度學習中常用的符號表達式

在論文寫作過程中&#xff0c;常常涉及到一些關鍵的符號的表達&#xff0c;為了更加規范常用的一些符號表達&#xff0c;現將其總結如下&#xff08;該文件會持續性更新&#xff09;&#xff1a; 數字 x x x : 標量 x \mathbf{x} x : 向量 X \mathbf{X} X : 矩陣 X \mathsf{X}…

react naive 網絡框架源碼解析

本文取 react native 兩個區別很大的版本做分析&#xff08;0.76.5、0.53.3&#xff09; 一、0.76.5 版fetch 全流程排查 1、JS 端的實現 隨手寫一個fetch&#xff0c;點開。 我們這里常用的還是手機端&#xff0c;因此選擇 react-native&#xff0c;react-native-windows …

OpenCV 圖形API(81)圖像與通道拼接函數-----透視變換函數warpPerspective()

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 對圖像應用透視變換。 函數 warpPerspective 使用指定的矩陣對源圖像進行變換&#xff1a; dst ( x , y ) src ( M 11 x M 12 y M 13 M 31 x…

深度學習在油氣地震資料反卷積中的應用

深度學習在油氣地震資料反卷積中的應用 基本原理 在油氣地震勘探中&#xff0c;反卷積(Deconvolution)是一種重要的信號處理技術&#xff0c;用于提高地震資料的分辨率。傳統方法(如維納濾波、預測反卷積等)存在對噪聲敏感、假設條件嚴格等局限。深度學習方法通過數據驅動的方…

Java開發者面試實錄:微服務架構與Spring Cloud的應用

面試場景 面試官: 請介紹一下你的基本情況。 程序員: 大家好&#xff0c;我叫張小明&#xff0c;今年27歲&#xff0c;碩士學歷&#xff0c;擁有5年的Java后端開發經驗。主要負責基于Spring Boot開發企業級應用&#xff0c;以及微服務架構的設計和實現。 面試官: 好的&#…

?Spring + Shiro 整合的核心要點及詳細實現說明

在 Spring 項目中集成 Apache Shiro 可以實現輕量級的安全控制&#xff08;認證、授權、會話管理等&#xff09;。以下是 ?Spring Shiro 整合的核心要點及詳細實現說明&#xff1a; 一、Spring 與 Shiro 整合的核心組件 ?組件??作用?ShiroFilterFactoryBean創建 Shiro 過…

網絡編程核心技術解析:從Socket基礎到實戰開發

網絡編程核心技術解析&#xff1a;從Socket基礎到實戰開發 一、Socket編程核心基礎 1. 主機字節序與網絡字節序&#xff1a;數據傳輸的統一語言 在計算機系統中&#xff0c;不同架構對多字節數據的存儲順序存在差異&#xff0c;而網絡通信需要統一的字節序標準&#xff0c;這…

SQLark可以支持PostgreSQL了,有哪些新功能?

SQLark&#xff08;百靈連接&#xff09;是一款國產的數據庫開發和管理工具&#xff0c;用于快速查詢、創建和管理不同類型的數據庫系統&#xff0c;支持達夢、Oracle 和 MySQL 數據庫。 最新發布的 SQLark V3.4 版本新增了對 PostgreSQL 數據庫的支持。我試用了一下&#xff…