STL 容器設計初衷是存放物件實體(value semantics),而非指標。直接存放指標雖然語法合法,但極易導致記憶體洩漏、雙重釋放、淺拷貝等問題。然而在以下情境,使用指標(或智慧指標)確有必要:
本文介紹一個指標封裝類別,讓 STL 容器能安全管理指標,並支援排序、比較等演算法需求。
==、<、>),支援 set、sort 等演算法() 取得原始指標// 通用指標封裝樣板
template <typename T>
class PtrWrapper {
T* ptr_;
public:
// 建構/拷貝/指派/解構
PtrWrapper(T* p = nullptr) : ptr_(p) {}
PtrWrapper(const PtrWrapper& other) : ptr_(other.ptr_) {}
PtrWrapper& operator=(const PtrWrapper& other) {
ptr_ = other.ptr_; return *this;
}
~PtrWrapper() {} // 注意:此處不刪除指標,屬淺拷貝語意
// 取得原始指標
T* get() const { return ptr_; }
T* operator->() const { return ptr_; }
T& operator*() const { return *ptr_; }
// 比較運算子(委派給 T 的運算子)
bool operator==(const PtrWrapper& rhs) const {
return ptr_ && rhs.ptr_ ? *ptr_ == *rhs.ptr_ : ptr_ == rhs.ptr_;
}
bool operator<(const PtrWrapper& rhs) const {
return ptr_ && rhs.ptr_ ? *ptr_ < *rhs.ptr_ : ptr_ < rhs.ptr_;
}
bool operator>(const PtrWrapper& rhs) const { return rhs < *this; }
};
為了讓 PtrWrapper 的比較運算子正常運作,被指向的類別 T 必須實作:
operator==、operator< 等比較運算子class Data {
int id_;
public:
Data(int id) : id_(id) {}
Data(const Data& d) : id_(d.id_) {}
Data& operator=(const Data& d) { id_ = d.id_; return *this; }
~Data() {}
bool operator==(const Data& rhs) const { return id_ == rhs.id_; }
bool operator<(const Data& rhs) const { return id_ < rhs.id_; }
int getId() const { return id_; }
};
#include <list>
#include <set>
#include <deque>
#include <algorithm>
#include <iostream>
void print(const PtrWrapper<Data>& pw) {
std::cout << pw->getId() << ' ';
}
int main() {
// 1. 建立指標陣列
PtrWrapper<Data> arr[5];
for (int i = 0; i < 5; ++i)
arr[i] = PtrWrapper<Data>(new Data(i * i)); // 動態配置
// 2. 打亂順序
std::random_shuffle(arr, arr + 5);
// 3. 複製到 list
std::list<PtrWrapper<Data>> lst;
std::copy(arr, arr + 5, std::back_inserter(lst));
std::for_each(lst.begin(), lst.end(), print); std::cout << '\n';
// 4. 複製到 set(自動排序、去重)
std::set<PtrWrapper<Data>, std::greater<PtrWrapper<Data>>> st;
std::copy(lst.begin(), lst.end(), std::inserter(st, st.begin()));
std::for_each(st.begin(), st.end(), print); std::cout << '\n';
// 5. 複製到 deque
std::deque<PtrWrapper<Data>> dq;
std::copy(lst.begin(), lst.end(), std::back_inserter(dq));
std::for_each(dq.begin(), dq.end(), print); std::cout << '\n';
// 6. 釋放記憶體(PtrWrapper 不負責刪除,需手動管理)
for (int i = 0; i < 5; ++i) delete arr[i].get();
return 0;
}
上述 PtrWrapper 採用淺拷貝,不負責刪除指標。實務上必須明確擁有權歸屬,或改用:
std::shared_ptr<T>(C++11):共享擁有權,引用計數自動釋放std::unique_ptr<T>(C++11):獨佔擁有權,需搭配移動語意存入容器boost::ptr_container:專為指標設計的容器,自動管理生命週期存放基類指標(PtrWrapper<Base>)可實現異質容器,但需注意:
std::vector<std::unique_ptr<Base>>BCB TList 超過 5000 元素效能明顯下降且非型別安全,STL 容器配合封裝類別可同時獲得型別安全、跨平台可攜性與優異效能。