刨析一下C++構造析構函數能不能聲明為虛函數的背後機理?

来源:https://www.cnblogs.com/englyf/archive/2022/08/27/16631774.html
-Advertisement-
Play Games

以下內容為本人的著作,如需要轉載,請聲明原文鏈接 微信公眾號「englyf」https://www.cnblogs.com/englyf/p/16631774.html 先說結論: 構造函數不能聲明為虛函數,析構函數可以聲明為虛函數。 構造函數可以聲明為虛函數嗎? 虛函數表裡都存了些什麼東西?不是金 ...


以下內容為本人的著作,如需要轉載,請聲明原文鏈接 微信公眾號「englyf」https://www.cnblogs.com/englyf/p/16631774.html


先說結論:

構造函數不能聲明為虛函數,析構函數可以聲明為虛函數。

構造函數可以聲明為虛函數嗎?


虛函數表裡都存了些什麼東西?不是金,不是銀,是對應類里聲明為虛函數的成員地址。在編譯期,每個類的虛函數表即被分配和生成。同一個類的所有實例對象都是共用這個虛函數表的,那麼每個實例對象也就會隱含有一個成員指針變數專門用來存儲虛函數表的地址。這個隱含的成員指針變數需要在實例對象初始化後才會指向虛函數表。

很顯然,對象不能在沒有初始化之前就知道自己對應的虛函數表在哪裡,因此也不能在對象初始化之前調用訪問虛函數表的內容(虛函數)。是不是在對象初始化之前就不能調用訪問這些虛函數成員了?不是這個意思,這裡說的不能僅限於通過虛函數表來訪問(比如派生後的類實例對象通過父類的指針變數訪問調用虛函數成員),也就是動態訪問時才需要虛函數表的信息。不過,就算某個成員函數被聲明為虛函數時,也可以通過類的靜態特性合法訪問的,這是無關痛癢的題外話,評價____。

對象的初始化就是依賴於構造函數的執行,首先是找到最上一層父類的構造函數並執行,然後逐層往下執行構造函數,直到執行完當前類(被實例化的類)的構造函數,這個過程不需要虛函數表的任何信息,在編譯期就確定了所有需要的信息了。在構造函數執行之前,對象還無法確定自身的虛函數表在哪裡,又怎麼從虛函數表裡查找對應的虛構造函數呢?如果把構造函數聲明為虛函數,那麼意義在哪兒呢?想來想去,可能費勁打造出一把刀刃能削鐵如泥的好刀,卻只是在需要敲釘子時,想起用這把刀的刀背。

所以,很明顯構造函數不能聲明為虛函數。

析構函數可以聲明為虛函數嗎?


先看下麵的代碼,析構函數不聲明為虛函數時,

#include <iostream>
using namespace std;

class Base
{
public:
    Base() {
        cout << "Base constructor" << endl;
    }
    ~Base() {
        cout << "Base destructor" << endl;
    }
};

class Derived : public Base
{
public:
    Derived() {
        cout << "Derived constructor" << endl;
    }
    ~Derived() {
        cout << "Derived destructor" << endl;
    }
};

class Derived_again : public Derived
{
public:
    Derived_again() {
        cout << "Derived_again constructor" << endl;
    }
    ~Derived_again() {
        cout << "Derived_again destructor" << endl;
    }
};

int main()
{
    Base *obj = new Derived();
    cout << "----" << endl;
    delete obj;
    return 0;
}

看看編譯後執行的結果(這裡用的編譯器是g++)

Base constructor
Derived constructor
----
Base destructor

從上面的輸出來看,類Derived的析構函數沒有被調用到,這會導致典型的問題--記憶體泄漏。

然後把所有析構函數聲明為虛函數,重新編譯再看看執行結果

Base constructor
Derived constructor
----
Derived destructor
Base destructor

可以看到,需要調用的析構函數都調用了。

那麼怎麼去理解上面這段代碼的執行邏輯呢?

delete obj;

銷毀對象時,系統執行的邏輯有兩種情況。

一種是,如果析構函數沒有被聲明為虛函數時,那麼在指針obj指向的對象的虛函數表裡,是找不到虛析構函數的。由於系統無法往下查找派生類的內容,而且變數obj被聲明為某個類(上面代碼對應的是Base)的指針類型,那麼系統就轉為調用這個類(Base)的成員析構函數,這裡利用的是靜態特性。

另一種是,如果析構函數被聲明為虛函數時,先在指針obj指向的對象的虛函數表裡,嘗試尋找當前對象obj的虛析構函數,找到後執行它。對應代碼,被實例化的類是Derived,對象obj的虛析構函數地址應該指向類Derived的成員析構函數。

上面兩種情況中,找到第一個析構函數並執行後,會按照靜態特性(也就是按照編譯期生成的信息),逐層往上一層父類查找成員析構函數並執行,直到最上一層的父類的析構函數被執行完畢為止。

於是,第一種情況下,類Derived的成員析構函數被漏掉執行了,這會導致類Derived所申請的資源沒有對應的析構函數來執行釋放,記憶體泄漏發生地那麼順理成章。

可以看到,動態特性可以把事情玩得妥妥噹噹,面向對象的高級感可能就來自這裡。

總結一下,很明顯,析構函數可以聲明為虛函數,但不是必須。某些情況下,也是必須的,比如,當類指針指向的是該類的子類實例時,析構函數必須聲明為虛函數,以防止記憶體泄漏。


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

-Advertisement-
Play Games
更多相關文章
  • 對象及日期定時器 Date日期 日期對象的定義(使用new關鍵詞) 1.獲取當前的時間(本地的時間) var date = new Date() //不傳參就是獲取當前時間 2.獲取指定的時間 var date = new Date(123456) //一個參數毫秒值 將這個毫秒值去加上對應的197 ...
  • 字元串 字元串 字元串也是一個數據結構,將同樣的內容串在一塊。因為在對應的js裡面字元串屬於一個值類型(值類型是常量 常量是不能變)。字元串是不能改變的。結合數據結構裡面串也是一個存儲結構,作為存儲結構增刪改查的方法(字元串的增刪改查 不能針對於本身 而是返回一個新的字元串) 字元串的聲明 1.值類 ...
  • 數組 數據結構 數據的結構 (邏輯結構 存儲結構 演算法) 存儲結構 (數據存儲的結構方式) 線性結構 數組(順序表) 隊列 棧 堆 鏈表 非線性結構 樹 圖hash (散列表) 只要是能存數據的容器 就必須具備增刪改查的方法 數組 數組概述:數組固定一類數據的組合(一般情況下我們數組裡面的數據類型一 ...
  • 淺拷貝 只拷貝第一層的值,其他後面拷貝的是地址。 示例 使用u盤在一臺電腦上拷貝文件,使用淺拷貝拷貝的相當於快捷方式。 第一層兩個內容不一樣,其他每層都是指向同一個文件 實現淺拷貝的方法 Object.assign (實現淺拷貝) let obj = { user:{ name: " jack" } ...
  • 1. 動態SQL(核心) 1.1 簡介 Mybatis框架的動態SQL技術是一種根據特定條件動態拼裝SQL語句的功能,它存在的意義是為瞭解決拼接SQL語句字元串時的難點問題。 比如: 我們在多條件查詢的時候會寫這樣的語句: select * from sys_user where 1=1 and 再 ...
  • 摘要:由華為技術大咖VS派拉軟體CTO為大家詳解雲原生架構下的身份管理平臺,構建雲安全數字身份入口。 本文分享自華為雲社區《DTSE Tech Talk | 第4期:雲原生架構下的數字身份治理實踐》,作者: 華為雲社區精選。 DTSE Tech Talk是華為雲開發者聯盟推出的技術公開課,解讀雲上前 ...
  • 《機器人SLAM導航核心技術與實戰》第1季:第2章_C++編程範式 視頻講解 【第1季】2.第2章_C++編程範式-視頻講解 【第1季】2.1.第2章_C++編程範式-C++工程的組織結構-視頻講解 【第1季】2.2.第2章_C++編程範式-C++代碼的編譯方法-視頻講解 【第1季】2.3.第2章_ ...
  • 操作系統的介紹 1.什麼是操作系統 操作系統其實就是一個協調、管理、控制電腦硬體資源和軟體資源的一個控製程序 2.為什麼要有操作系統 (1)可以控制電腦硬體的基本運行 (2)把操作硬體的複雜操作封裝成一個簡單的功能,交給上層的應用程式使用。例如文件就是操作系統提供給應用程式的一種功能! 3.程式 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...