C++智能指针原理

简介

智能指针就是对指针进行封装,使其提供特有的功能。

unique_ptr:封装了原始指针使其只能在同一时刻被同一对象拥有,并且在离开作用域时会自动销毁。

shared_ptr: 封装了原始指针,利用引用技术技术,实现多个对象同时共享一个指针,并且在所有对象都离开作用域时释放内存.

weak_ptr : 用来解决shared_ptr带来的循环计数问题,而且weak_ptr中的lock函数保证是线程安全

 

实现一个简单的unique_ptr

unique_ptr主要功能 :

  1.  不能够赋值,拷贝,允许移动
  2. operator* 实现
  3. operator→ 实现
  4. default_delete实现
  5. reset, release, get实现
template <class Tp, class Dp = default_delete<Tp>>
class unique_ptr {
public:
    typedef Tp element_type;
    typedef Delete delete_type;
    typedef element_type* pointer;
    typedef element_type& reference;
public:
    unique_ptr(pointer data = nullptr) : data_(data) {}
    unique_ptr(pointer data, delete_type del) : data_(data), del_(del) {}
 
    ~unique_ptr() { clear(); }
     
    // 不允许赋值,拷贝
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;
 
    // 允许移动
    unique_ptr(unique_ptr&& up)
        : data_(up.relase()), del_(up.del_) {}
     
    unique_ptr& operator=(unique_ptr&& up) {
        if (&up != *this) {
            reset(up.release());
            del_ = up.del_;
        }
        return *this;
    }
private:
    Tp* data_;
    Dp del_;
};

 上面的代码实现了第一步,不允许赋值拷贝,允许移动,代码非常简单,unqiue_ptr有两个模板参数,第一个是指针类型,第二个参数就是对应着这个指针的删除器

 

reset, release和clear实现 :

pointer release() noexcept{
    auto res = data_;
    data_ = nullptr;
    return res;
}
 
void reset(pointer p = pointer()) noexcept {
    auto tmp = data_;
    data_ = p;
    if (tmp)
        del_(tmp);
}
 
void clear() const {
    del_(data_);
    data_ = nullptr;
}

elease函数的作用就是放弃原生指针的所有权,reset的功能是用一个新指针来替换原来的指针

 

operator*, operator→实现:

reference operator*() const { return (*data_); }
pointer operator->() const noexcept { return data_; }

 

defalut_delete实现 :

template <typename T>
struct default_delete {
    void operator()(T* ptr) {
        if (ptr)
            delete ptr;
    }
};
 
template <typename T>
struct default_delete<T []> {
    void operator()(T* ptr) {
        if (ptr)
            delete []ptr;
    }
}

    这两个default_delete利用模板偏特化技术分别实现了删除普通指针以及数组指针, 到这里unique_ptr就已经实现完了,代码并不复杂,很简单,最后来看一个make_unique

tempalte <typename T, typename... Args>
unique_ptr<T> make_unique(Args&&... args)
{
    return unique_ptr<T>(new T(std::forward<Args>(args)...));
}

 

 

实现一个简单的shared_ptr

shared_ptr主要功能:

  1. 引用计数
  2. operator*
  3. operator→
  4. 赋值拷贝构造函数

引用计数实现:

template <typename ClassName, typename T>
class RefCount {
    friend ClassName;
    RefCount(T* p) : pointer_(p), count(1) {}
    ~RefCount() { delete pointer_; }
 
    T* pointer_;
    size_t count_;
};

    这个引用计数实现的非常简单,仅仅包含了一个指针和一个计数值,真正的操作放到了Shared_ptr里面了

operator*, operator→, 赋值,拷贝构造函数实现:

template <typename T>
class shared_ptr {
public:
    typedef T value_type;
    typedef T* pointer;
    typedef T& reference;
public:
    shared_ptr(value_type* ptr) : ptr_(new RefCount<shared_ptr, T>(ptr)) {}
     
    shared_ptr(const shared_ptr& sp) : ptr_(sp) {
        ++ptr_->count_;
    }
    shared_ptr& operator=(const shared_ptr& rhs) {
        if (&rhs == this) {
            // 由于rhs需要赋值给*this,所以将rhs的计数先+1
            ++rhs.ptr_->count_;
            // 由于*this 需要抛弃掉本身保存的指针,所以将计数-1并判断是否已经是最后一个
            if (--ptr_->count_ == 0)
                delete ptr_;
            ptr_ = rhs.ptr_;
        }
        retur *this;
    }
     
    ~shared_ptr() {
        if (--ptr_->count_ == 0)
            delete ptr_;
    }
 
    reference operator*() const { return *ptr_->pointer_;}
    pointer operator->() const { return &(operator*()); }
private:
    RefCount<shared_ptr, T>* ptr_;
};

shared_ptr中最主要的就是拷贝构造和赋值运算符以及析构函数中对count进行的管理,在析构的时候需要将count减1,并判断是否为0,为0就表示当前是最后一个引用这个指针的shared_ptr,需要释放资源,在赋值和拷贝时都需要将计数+1, 在来看一下make_shared.

template <typename T, typename... Args>
shared_ptr<T> make_shared(Args&&... args> {
    return shared_ptr<T>(new T(std::forward<Args>(args)...));
}

 

shared_ptr 多线程问题

由上面我们实现的代码可以看出,在对count进行操作时不是多线程安全的,在标准库的实现中引用计数是线程安全的,它在底层用的是原子操作,也就是说在多线程情况下它只会释放一次也时线程不安全的,但是在构造,swap,reset操作中不是线程安全的,所以在多线程中共享shared_ptr需要格外的小心,要么加锁来保证安全,或者用weak_ptr来代替shared_ptr

shared_ptr 循环引用问题

#include <iostream>
#include <memory>
using namespace std;
 
class B;
class A
{
public:// 为了省去一些步骤这里 数据成员也声明为public
shared_ptr<B> pb;
void doSomthing()
{
}
 
~A()
{
cout << "kill A\n";
}
};
 
class B
{
public:
shared_ptr<A> pa;
~B()
{
cout <<"kill B\n";
}
};
 
int main(int argc, char** argv)
{
shared_ptr<A> sa(new A());
shared_ptr<B> sb(new B());
if(sa && sb)
{
sa->pb=sb;
sb->pa=sa;
}
cout<<"sa use count:"<<sa.use_count()<<endl;
return 0;
}

 

 上面的代码运行结果为:sa use count:2, 注意此时sa,sb都没有释放,产生了内存泄露问题!!

为什么产生内存泄漏 : 由于A里面有一个B的shared_ptr, 所以在A析构之前B必须析构,但是是B里面又有一个A的shared_ptr, 所以在B析构之前,A必须析构,有木有发现逻辑全乱了,就是这样就导致了循环引用,也就内存泄漏了.

 

weak_ptr解决shared_ptr循环引用

#include <iostream>
#include <memory>
using namespace std;
  
class B;
class A
{
public:// 为了省去一些步骤这里 数据成员也声明为public
    weak_ptr<B> pb;
    void doSomthing()
    {
        shared_ptr<B> pp = pb.lock();
        if(pp)//通过lock()方法来判断它所管理的资源是否被释放
        {
            cout<<"sb use count:"<<pp.use_count()<<endl;
        }
    }
  
    ~A()
    {
        cout << "kill A\n";
    }
};
  
class B
{
public:
    shared_ptr<A> pa;
    ~B()
    {
        cout <<"kill B\n";
    }
};
  
int main(int argc, char** argv)
{
    shared_ptr<A> sa(new A());
    shared_ptr<B> sb(new B());
    if(sa && sb)
    {
        sa->pb=sb;
        sb->pa=sa;
    }
    sa->doSomthing();
    cout<<"sb use count:"<<sb.use_count()<<endl;
    return 0;
}

 

posted @ 2021-07-18 22:53  gd_沐辰  阅读(285)  评论(0编辑  收藏  举报