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,不过借由以上的探讨,可以理解自动管理资源的原理,并认清一件事实,认识自动管理资源的相关类原理是重要的一件事,以避免不可预期的行为。


展开阅读全文