shared_ptr


很多情况下,动态配置的对象会在不同的类实例间共享,很自然地就会引发一个问题,谁该负责删除这个被分享的、动态配置的对象?

答案可以很简单,最后一个持有动态配置对象的实例不再需要该对象时,该实例要负责删除对象,想采用这个答案,要解决的就是,怎么知道谁是最后一个持有对象的实例?

如果有个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;
}




展开阅读全文