許多 C++ 開發者僅把 `std::vector` 當作「自動釋放記憶體的陣列」,忽略其對 **介面設計** 的深遠影響。本文以「圖像匹配演算法」為例,對比三種介面演進,展示動態容器如何同時解決:記憶體洩漏風險、超大臨時分配、參數複雜度、使用者認知負擔。
struct PointInfo { long x, y, geo_x, geo_y; };
// 介面:呼叫端必須預先分配「整幅圖大小」的陣列
PointInfo* ImageMatch(FILE* left, FILE* right, long &validNum) {
long w = getWidth(left), h = getHeight(left);
PointInfo* result = new PointInfo[w * h]; // ❌ 超大分配,多數浪費
// ... 演算法填充前 validNum 個元素 ...
return result; // ❌ 呼叫端極易忘記 delete[]
}
痛點:分配 `w*h` 條目(如 4000×3000=12M 條,~192 MB),實際僅需幾百條;所有權轉移不清晰,極易洩漏。
void ImageMatch(FILE* left, FILE* right,
PointInfo* outBuf, long bufSize, long &validNum) {
// ... 填充 outBuf,不超過 bufSize ...
}
// 呼叫端
long w = getWidth(), h = getHeight();
PointInfo* buf = new PointInfo[w * h]; // 仍需超大分配
long valid = 0;
ImageMatch(left, right, buf, w*h, valid);
// 使用 buf[0..valid-1]
delete[] buf;
痛點:參數增至 5 個,呼叫端仍須分配最大可能空間,語義雜訊大(`bufSize` 易誤解)。
void ImageMatch(FILE* left, FILE* right,
std::vector<PointInfo> &outPoints) {
outPoints.clear(); // 確保乾淨狀態
outPoints.reserve(1024); // 預估典型匹配點數,避免多次擴容
// ... 演算法核心 ...
// 每找到一對匹配:
outPoints.push_back({x, y, gx, gy}); // 自動擴容、零管理負擔
}
// 呼叫端
std::vector<PointInfo> matches;
ImageMatch(left, right, matches);
// 直接使用 matches.size(), matches[i]
// 離開作用域自動釋放,零洩漏風險
| 維度 | 裸指針回傳 | 輸出緩衝區 | vector 參考 |
|---|---|---|---|
| 記憶體管理 | 呼叫端易忘釋放 | 呼叫端負責 | RAII 自動 |
| 分配策略 | 一次性超大 | 一次性超大 | 按需增長 |
| 介面參數數 | 3 | 5 | 3 |
| 語義清晰度 | 低(所有權模糊) | 中(需讀文檔) | 高(標準慣用語) |
| 擴展性 | 差 | 差 | 優(可攜帶元數據) |
「介面應該讓正確用法最簡單,錯誤用法最困難。」動態容器將「記憶體管理」從介面契約中移除,讓呼叫端專注於業務邏輯,是現代 C++ 介面設計的基石。