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

深入剖析 MFC CArray 動態陣列實作機制

     編輯:關於VC++

前言

在 VC++ 複雜專案開發中,動態管理陣列結構是常見需求。C++ 標準庫早期未提供動態陣列,MFC 推出的樣板類別 CArray 填補了此缺口。本文透過原始碼層級分析,揭示 CArray 內部運作原理,幫助開發者寫出高效能、記憶體安全的程式碼。

CArray 類別架構

CArray 繼承自 CObject,接受兩個樣板參數:

  • TYPE:陣列元素的資料型別
  • ARG_TYPE:函式傳參時的型別(通常為 TYPE& 以提升效能)

宣告範例:

CArray<Object, Object> var1;       // 傳值方式
CArray<Object, Object&> var2;     // 傳參考方式(效率較高)

核心成員變數

TYPE*  m_pData;      // 元素資料指標
int    m_nSize;      // 目前邏輯大小
int    m_nMaxSize;   // 實際配置容量
int    m_nGrowBy;    // 擴充增量

建構函式與初始化

CArray<TYPE, ARG_TYPE>::CArray() {
    m_pData = NULL;
    m_nSize = m_nMaxSize = m_nGrowBy = 0;
}

SetSize 核心邏輯深度解析

SetSize(int nNewSize, int nGrowBy = -1) 是記憶體管理的核心,分四大情況處理:

情況 1:縮減至 0 (nNewSize == 0)

  • 若已有資料,呼叫 DestructElements 逐一呼叫解構函式,再 delete[] 釋放記憶體。
  • 重置大小計數器。

情況 2:首次配置 (m_pData == NULL)

  • 以 new BYTE[nNewSize * sizeof(TYPE)] 配置原始記憶體(不呼叫建構函式)。
  • 再呼叫 ConstructElements 逐一執行 placement new 建構物件。

情況 3:容量足夠 (nNewSize <= m_nMaxSize)

  • 增長:對新增區段呼叫 ConstructElements。
  • 縮減:對移除區段呼叫 DestructElements(不釋放記憶體,避免頻繁重新配置)。

情況 4:需重新配置 (nNewSize > m_nMaxSize)

  • 計算新容量 nNewMax(考慮 nGrowBy)。
  • 配置新記憶體,memcpy 複製舊資料(位元級複製,不呼叫建構/解構)。
  • 對新增元素呼叫 ConstructElements。
  • 釋放舊記憶體,更新指標與計數器。

為什麼必須呼叫建構/解構函式?

若僅配置記憶體不建構:

  • 基本型別(int、long)無影響。
  • 類別型別:成員未初始化(如 ID 為隨機值),複雜類別更會導致邏輯錯誤。

若刪除不解構:

  • 擁有動態資源的類別(如 Person::name 指標)將造成記憶體洩漏。

ConstructElements / DestructElements 實作

// ConstructElements:placement new 呼叫建構函式
template<class TYPE>
AFX_INLINE void AFXAPI ConstructElements(TYPE* pElements, int nCount) {
    memset(pElements, 0, nCount * sizeof(TYPE)); // 位元清零
    for (; nCount--; pElements++)
        ::new ((void*)pElements) TYPE;           // 在既有記憶體上建構
}

// DestructElements:直接呼叫解構函式
template<class TYPE>
AFX_INLINE void AFXAPI DestructElements(TYPE* pElements, int nCount) {
    for (; nCount--; pElements++)
        pElements->~TYPE();
}

下標運算子 [] 的雙重實作

// const 版本:回傳複本(不可修改原陣列)
AFX_INLINE TYPE CArray<TYPE, ARG_TYPE>::operator[](int nIndex) const {
    return GetAt(nIndex);  // return m_pData[nIndex];
}

// 非 const 版本:回傳參考(可修改原陣列)
AFX_INLINE TYPE& CArray<TYPE, ARG_TYPE>::operator[](int nIndex) {
    return ElementAt(nIndex); // return m_pData[nIndex];
}

差異示範:

CArray<int, int&> arr; arr.SetSize(10);
int n = arr.GetAt(0);      // 複本
int& l = arr.ElementAt(0); // 參考
n = 10;  // arr[0] 不變
l = 20;  // arr[0] 變為 20

第二樣板參數採用參考型別的效能考量

Add(ARG_TYPE newElement) 內部呼叫 SetAtGrow,若 ARG_TYPE 為 Object(傳值),會在堆疊額外建構暫時物件;改為 Object& 則避免額外複製,顯著提升大型物件插入效能。

總結與建議

  • CArray 非邊界安全,ASSERT 僅在 Debug 生效,Release 下越界導致未定義行為。
  • 善用 SetSize 預先配置容量,減少重新配置開銷。
  • 元素為類別時,務必確保正確實作複製建構函式與指派運算子,或改用指標容器 CArray<T*, T*>。
  • 參考 MSDN 文件掌握完整介面(InsertAt、RemoveAt、FreeExtra 等)。

深入理解 CArray 實作細節,能讓你在效能敏感場合做出最佳容器選擇與調優決策。

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