auto_ptr


使用 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_ptrshared_ptr 等類別模版,可以根據不同資源管理需求來選用,因此不該再使用 auto_ptr,不過藉由以上的探討,可以理解自動管理資源的原理,並認清一件事實,認識自動管理資源的相關類別原理是重要的一件事,以避免不可預期的行為。