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

C++ 物件計數器實作:模板化、繼承與組合比較、繼承鏈陷阱與解決方案

     編輯:關於C++

基本需求

統計類別在執行期的實例數量:建構 +1、複製建構 +1、解構 -1。

初版:單一靜態計數器


class Counter {
    static size_t count;
public:
    Counter() { ++count; }
    Counter(const Counter&) { ++count; }
    ~Counter() { --count; }
    static size_t howMany() { return count; }
};
size_t Counter::count = 0;

缺點:所有使用此類的類別共享同一計數。

模板化:每類別獨立計數


template 
class Counter {
    static size_t count;
public:
    Counter() { ++count; }
    Counter(const Counter&) { ++count; }
    ~Counter() { --count; }
    static size_t howMany() { return count; }
};
template  size_t Counter::count = 0; // 可置於標頭檔

兩種整合方式

A. 組合 (嵌入成員)


class Widget {
    Counter c;
public:
    static size_t howMany() { return Counter::howMany(); }
};
  • 優點:語意清晰 (Widget 擁有 計數器)
  • 缺點:增加類別大小 (空類別最小 1 byte),需手動轉發 howMany()

B. 公開繼承


class Widget : public Counter {
    // 直接繼承 howMany()
};
  • 優點:零大小開銷 (空基類別優化 EBO),直接取得 howMany()
  • 缺點:引入 虛擬解構函數需求 (若有人 delete static_cast*>(new Widget))

解決虛擬解構問題:protected 解構函數


template 
class Counter {
public:
    Counter() { ++count; }
    Counter(const Counter&) { ++count; }
protected:
    ~Counter() { --count; } // 禁止 delete base*
public:
    static size_t howMany() { return count; }
private:
    static size_t count;
};

現在 Counter* p = new Widget; delete p; → **編譯錯誤**,避免未定義行為。

C. 私有繼承 (推薦折衷)


class Widget : private Counter {
public:
    using Counter::howMany; // 暴露靜態方法
};
  • 阻擋 Counter* p = new Widget; 隱性轉換
  • 保有 EBO 零大小優勢
  • 語意:Widget 依據 Counter 實作,非 是一種 Counter

繼承鏈陷阱:衍生類別計數污染基類


class SpecialWidget : public Widget, public Counter { };
  • SpecialWidget 計數正確 (自己的 Counter)
  • Widget 計數**錯誤**:每建構一個 SpecialWidget,Widget 建構函數執行 → Counter +1
  • 結果:Widget::howMany() = Widget 實例 + SpecialWidget 實例

根本限制

模板計數器無法正確區分繼承鏈中的基類與衍生類實例,因為基類建構函數在衍生類建構時必然執行。

替代方案:外部登記表


template 
class Registry {
    static std::atomic count;
public:
    static void add() { ++count; }
    static void remove() { --count; }
    static size_t howMany() { return count; }
};

// 使用者在每個建構/解構手動呼叫
class Widget {
public:
    Widget() { Registry::add(); }
    Widget(const Widget&) { Registry::add(); }
    ~Widget() { Registry::remove(); }
    static size_t howMany() { return Registry::howMany(); }
};

優點:完全控制計數時機,可在衍生類決定是否呼叫基類登記。缺點:需手動維護,易忘。

總結比較表

核心啟示

物件計數看似簡單,實則觸及 EBO、虛擬解構、繼承語意、模板具現化隔離 等核心 C++ 機制。理解各方案權衡,比複製貼上代碼更重要。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
方案大小開銷使用便利性繼承鏈正確性推薦場景
組合+1 byte需轉發同繼承單一類別、不介意大小
public 繼承 + protected dtor0 (EBO)直接繼承失效單一類別、效能敏感
private 繼承 + using0 (EBO)一行轉發失效單一類別、語意正確
外部登記表0手動呼叫可控複雜繼承體系