• 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
    ";
    }
    };
     
    class B
    {
    public:
    shared_ptr<A> pa;
    ~B()
    {
    cout <<"kill B
    ";
    }
    };
     
    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
    ";
        }
    };
      
    class B
    {
    public:
        shared_ptr<A> pa;
        ~B()
        {
            cout <<"kill B
    ";
        }
    };
      
    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;
    }

  • 相关阅读:
    转载 Oracle常用函数
    转载:ASP.NET Request对象使用实例浅析
    JQuery's Ajax request a datatable
    转载 WCF VS ASPNET WEB SERVICE
    转载 浅谈C#中构造函数与析构函数
    javascript正则表达式 实现Trim()
    转载: NET中使用log4net记录日志
    Consuming WCF / ASMX / REST service using JQuery
    asp.net下使用Request.Form获取非服务器控件的值的方法
    如何把Access转成SQL Server的方法介绍
  • 原文地址:https://www.cnblogs.com/gd-luojialin/p/15028157.html
Copyright © 2020-2023  润新知