很多情況下,動態配置的物件會在不同的類別實例間共享,很自然地就會引發一個問題,誰該負責刪除這個被分享的、動態配置的物件?
答案可以很簡單,最後一個持有動態配置物件的實例不再需要該物件時,該實例要負責刪除物件,想採用這個答案,要解決的就是,怎麼知道誰是最後一個持有物件的實例?
如果有個 SharedPtr
可以管理動態配置物件,SharedPtr
實例共用一個計數器,它記錄有幾個 SharedPtr
實例共享該物件,每多一個 SharedPtr
實例共享物件時,計數器增一,若共享物件的 SharedPtr
實例被摧毀時,計數器減一,若有個 SharedPtr
實例發現計數器為零時,就將共享的物件刪除。
當然,想實作這樣的 SharedPtr
也是點挑戰性,不過若能實現,對 C++ 11 以後標準程式庫提供的 shared_ptr
就會更能掌握,就來實現個簡單版本吧!
#include <iostream>
using namespace std;
template<typename T>
class SharedPtr {
using Deleter = void (*)(T*);
T* p = nullptr;
size_t* pctr = nullptr; // 參考計數
Deleter del = nullptr;
// 被交換的 sharedPtr,參考計數是否減一
// 就看還有沒有在其他處被引用
void swap(SharedPtr& sharedPtr) {
using std::swap;
swap(this->p, sharedPtr.p);
swap(this->pctr, sharedPtr.pctr);
swap(this->del, sharedPtr.del);
}
public:
SharedPtr(T* p = nullptr, Deleter del = nullptr) :
p(p), pctr(new size_t(p != nullptr)), del(del) {}
SharedPtr(const SharedPtr& sharedPtr) : p(sharedPtr.p), pctr(sharedPtr.pctr), del(sharedPtr.del) {
// 參考計數加一
++*(this->pctr);
}
SharedPtr(SharedPtr&& sharedPtr) : SharedPtr() {
this->swap(sharedPtr);
}
// sharedPtr 參數在執行過後就摧毀了,參考計數會減一
SharedPtr& operator=(SharedPtr sharedPtr) {
this->swap(sharedPtr);
return *this;
}
~SharedPtr() {
if(this->p == nullptr) {
return;
}
// 參考計數減一
if(--*(this->pctr) == 0) {
// 若參考計數為零,刪除資源
this->del ? this->del(this->p) : delete this->p;
delete pctr;
}
}
void reset(T *p = nullptr, Deleter del = nullptr) {
// wrapper 參數在執行過後就摧毀了,參考計數會減一
SharedPtr wrapper(p, del);
this->swap(wrapper);
}
// 令 SharedPtr 行為像個指標
T& operator*() { return *p; }
T* operator->() { return p; }
};
class Foo {
public:
int n;
Foo(int n) : n(n) {}
~Foo() {
cout << n << " Foo deleted" << endl;
}
};
int main() {
SharedPtr<Foo> f1(new Foo(10));
SharedPtr<Foo> f2(new Foo(20));
SharedPtr<Foo> f3(f1);
f2 = f1;
f3 = SharedPtr<Foo>(new Foo(30));
SharedPtr<int> arr(new int[3] {1, 2, 3}, [](int *arr) { delete [] arr; });
return 0;
}
這個簡單版本也考慮了自訂刪除器的指定,你可能會發現,怎麼與 unique_ptr 不太一樣,這是因為 shared_ptr
的刪除器是共享的,不若 unique_ptr
是各自管理著一個資源,而有各自的刪除器,在實作上,必須得在執行時期判斷是否有指定刪除器,決定要使用刪除器,還是 delete
。
C++ 11 提供了 shared_ptr
,定義在 memory
標頭,上面的範例基本上就是模仿了 shared_ptr
,來看看 shared_ptr
的使用:
#include <iostream>
#include <memory>
using namespace std;
class Foo {
public:
int n;
Foo(int n) : n(n) {}
~Foo() {
cout << n << " Foo deleted" << endl;
}
};
int main() {
shared_ptr<Foo> f1(new Foo(10));
shared_ptr<Foo> f2(new Foo(20));
shared_ptr<Foo> f3(f1);
f2 = f1;
f3 = shared_ptr<Foo>(new Foo(30));
shared_ptr<int> arr(new int[3] {1, 2, 3}, [](int *arr) { delete [] arr; });
return 0;
}
雖然可以直接建構 shared_ptr
實例,然而在不指定刪除器的情況下,建議透過 make_shared
,可以避免使用 new
:
#include <iostream>
#include <memory>
using namespace std;
class Foo {
public:
int n;
Foo(int n) : n(n) {}
~Foo() {
cout << n << " Foo deleted" << endl;
}
};
int main() {
auto f1 = make_shared<Foo>(10);
auto f2 = make_shared<Foo>(20);
auto f3(f1);
f2 = f1;
f3 = make_shared<Foo>(30);
return 0;
}
shared_ptr
實例可以透過 unique
方法,得知動態配置的物件是否與其他 shared_ptr
實例共享,透過 use_count
方法可以取得參考計數,shared_ptr
沒有像 unique_ptr
提供有可使用下標運算子的版本,本身也不支援加、減運算,因此對於動態配置的連續空間,若要取得指定空間的值,必須透過 get
取得管理的資源。例如:
#include <iostream>
#include <memory>
using namespace std;
int main() {
shared_ptr<int> arr(new int[3] {1, 2, 3}, [](int *arr) { delete [] arr; });
for(int *p = arr.get(), i = 0; i < 3; i++) {
cout << *(p + i) << endl;
}
return 0;
}