使用 new
動態配置的物件,在不需要使用時必須以 delete
刪除,然而動態記憶體配置很容易發生忘了delete
,如果有個方式可以自動刪除資源就好了!
若能建立一個非動態配置的物件,該物件管理著動態配置的對象,因為非動態配置的物件在不使用時會自動清除,若在解構式中對動態配置的物件進行 delete
的動作,是不是就不用擔心忘了 delete
的問題?
要實現這件事有許多面向必須得考慮,目標先不要太遠大,先從基本的開始考慮。
首先,它可以管理任意的類別型態,這可以定義模版;其次,管理動態配置對象的物件,行為上得像個指標,也就是必須支援 *
、->
操作,這倒是可以透過重載 *
、->
來達成;另外,物件被用來實例化或指定給另一物件時,誰該負責最後的資源刪除?而原本物件管理的資源怎麼辦?
若先來做個簡單的考量,物件被用來實例化另一物件時,管理資源的動作就交給新的物件,被指定給另一物件時,原物件管理的資源就釋放,並接管另一物件的資源,按照以上的想法,一個基本的 AutoPtr
管理類別就會像是:
#include <iostream>
using namespace std;
template<typename T>
class AutoPtr {
T* p;
public:
AutoPtr() = default;
AutoPtr(T* p) : p(p) {}
// 接管來源 autoPtr 的資源
AutoPtr(AutoPtr<T> &autoPtr) : p(autoPtr.p) {
autoPtr.p = nullptr;
}
// 刪除管理的資源
~AutoPtr() {
if(this->p != nullptr) {
delete this->p;
}
}
// 原管理資源被刪除,接管來源 autoPtr 的資源
AutoPtr<T>& operator=(AutoPtr<T>& autoPtr) {
if(this->p) {
delete p;
}
this->p = autoPtr.p;
autoPtr.p = nullptr;
return *this;
}
// 令 AutoPtr 行為像個指標
T& operator*() { return *(this->p); }
T* operator->() { return this->p; }
};
class Foo {
public:
int n;
Foo(int n) : n(n) {}
~Foo() {
cout << "Foo deleted" << endl;
}
};
void foo(int n) {
AutoPtr<Foo> f(new Foo(n));
cout << f->n << endl;
}
int main() {
foo(10);
return 0;
}
這是個自動管理資源的簡單實作,foo
中動態配置的 Foo
實例被 AutoPtr
管理,f
是區域的,foo
執行結束後 f
會被摧毀,因而自動刪除了管理的資源,因此執行結果會是:
10
Foo deleted
然而,這個實作用來建構另一 AutoPtr
或指定給另一 AutoPtr
實例時,資源會被接管,若忘了這件事,如下使用,就會出問題:
...略
void foo(AutoPtr<Foo> f) {
cout << f->n << endl;
}
int main() {
AutoPtr<Foo> f(new Foo(10));
foo(f); // 顯示 10、Foo deleted
cout << f->n << endl; // 不可預期行為
return 0;
}
AutoPtr
顯然地,也不能用來管理動態配置而來的連續空間,因為它並沒有使用 delete []
來刪除資源。
實際上,在 C++ 98 就提供有 auto_ptr
,定義在 memory
標頭檔,大致原理就像以上的 AutoPtr
實作,如果這麼用是沒問題:
#include <iostream>
#include <memory>
using namespace std;
class Foo {
public:
int n;
Foo(int n) : n(n) {}
~Foo() {
cout << "Foo deleted" << endl;
}
};
void foo(int n) {
auto_ptr<Foo> f(new Foo(n));
cout << f->n << endl;
}
int main() {
foo(10);
return 0;
}
實際上,auto_ptr
已經被廢棄了(deprecated),因此編譯時會產生警訊,被廢棄的原因就跟方才的 AutoPtr
類似,容易忽略了資源被接管的問題,例如:
#include <iostream>
#include <memory>
using namespace std;
class Foo {
public:
int n;
Foo(int n) : n(n) {}
~Foo() {
cout << "Foo deleted" << endl;
}
};
void foo(auto_ptr<Foo> f) {
cout << f->n << endl;
}
int main() {
auto_ptr<Foo> f(new Foo(10));
foo(f); // 顯示 10、Foo deleted
cout << f->n << endl; // 不可預期行為
return 0;
}
實際上,C++ 11 提供了 unique_ptr
、shared_ptr
等類別模版,可以根據不同資源管理需求來選用,因此不該再使用 auto_ptr
,不過藉由以上的探討,可以理解自動管理資源的原理,並認清一件事實,認識自動管理資源的相關類別原理是重要的一件事,以避免不可預期的行為。