首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

建構函式呼叫虛擬函式、視圖狀態持久化與 POD 型別深度解析

     編輯:VC 教學

本文涵蓋三個核心 C++/.NET 主題:建構函式中呼叫虛擬函式的 C++/C# 差異、檔案開啟對話方塊視圖模式持久化、POD(Plain Old Data)型別定義與應用。

一、建構函式/解構函式中呼叫虛擬函式:C++ vs C#

C++ 行為

C++ 中,基底類別建構函式執行時,衍生類別尚未建構,虛擬函式表(vtable)尚未完成初始化。因此從基底建構函式呼叫虛擬函式,會解析至基底類別的實作,而非最底層衍生類別。同理,解構函式執行時衍生部分已銷毀,呼叫虛擬函式同樣解析至基底實作。

class Base {
public:
    Base() { virtualFunc(); } // 呼叫 Base::virtualFunc
    virtual void virtualFunc() { cout << "Base"; }
};
class Derived : public Base {
public:
    Derived() { }
    void virtualFunc() override { cout << "Derived"; }
};
// new Derived() 輸出 "Base"

此行為雖為 C++ 基本常識,但常導致微妙錯誤,公認屬不良實踐。

C# 行為

C#(及所有 .NET 語言)中,托管物件一創建即為其最終型別。建構函式呼叫虛擬函式會解析至最底層衍生實作,即使衍生建構函式尚未執行。解構函式(Finalize)同理。

class Base {
    public Base() { VirtualMethod(); }
    public virtual void VirtualMethod() { Console.WriteLine("Base"); }
}
class Derived : Base {
    public Derived() { }
    public override void VirtualMethod() { Console.WriteLine("Derived"); }
}
// new Derived() 輸出 "Derived"

設計原因

.NET 垃圾收集器需知物件大小以回收記憶體。若採 C++ 分階段建構,同一型別物件在建構途中大小可能不同,GC 需額外邏輯追蹤部分建構物件,增加複雜度與效能損耗。為簡化 GC、提升速度,.NET 採用「一次建構為最終型別」模型。詳細討論見 Raymond Chen 部落格《The Old New Thing》。

二、檔案開啟對話方塊視圖模式持久化

問題:如何讀取/儲存使用者上次選擇的視圖模式(圖示、縮圖、清單、詳細資料)?

取得目前視圖模式

透過 FindWindowEx 找到 SysListView32 控制項,呼叫 CListCtrl::GetView() 取得 LV_VIEW_* 常數。但圖示模式與縮圖模式皆回傳 LV_VIEW_ICON,需進一步區分。

區分圖示與縮圖:圖示間距法

發送 LVM_GETITEMSPACING 訊息取得圖示間距:

CSize sz = CSize(plc->SendMessage(LVM_GETITEMSPACING));
if (sz.cx > GetSystemMetrics(SM_CXICONSPACING))
    // 縮圖模式(間距大於系統預設)
else
    // 圖示模式

持久化實作

  • 對話方塊銷毀時,於 CListViewShellWnd::OnDestroy 儲存模式至成員變數。
  • CPersistOpenDlg 解構函式呼叫 WriteProfileInt 寫入使用者設定檔(或登錄檔)。
  • 啟動時,CPersistOpenDlg 發送初始化訊息,GetProfileInt 讀取並 SetViewMode 還原。
  • 必須用 PostMessage 延遲初始化,因 WM_INITDIALOG/CDN_INITDONE 觸發時對話方塊尚未完全就緒。

建議使用 CWinApp::SetRegistryKey 導向登錄檔儲存,而非 INI 檔。

三、POD 型別概念與應用

定義

POD = Plain Old Data。指與 C 相容的純資料型別:無虛擬函式、無基底類別、無使用者定義建構/複製/指派/解構函式。例如 struct RECT、int、POINT 皆為 POD;CString、CPoint(有建構函式)則非 POD。

特性

  • 可用 memcpy 逐位元複製。
  • 可不初始化(如 struct RECT r; 值未定義)。
  • 非 POD 型別通常需建構函式初始化。

.NET 托管 C++ 中的限制

托管類別(__value、__gc)僅能內嵌原生 POD 型別。嘗試內嵌 CPoint 會產生 C3633 錯誤:「因 CPoint 有預設建構函式,無法定義為托管類別成員」。原因:POD 可安全逐位元複製,無需擔心建構函式、虛擬表初始化等機制。

__gc class Circle {
    Point m_center;     // OK:POD
    // CPoint m_center; // Error C3633
};

理解 POD 對跨語言互通、序列化、高效能資料結構設計至關重要。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved