ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

C++智能指针原理

2021-07-18 23:01:58  阅读:175  来源: 互联网

标签:delete C++ 智能 operator shared unique data ptr 指针


简介

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

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;
}

 

标签:delete,C++,智能,operator,shared,unique,data,ptr,指针
来源: https://www.cnblogs.com/gd-luojialin/p/15028157.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有