2.智能指针
智能指针
基础知识
智能指针的智能二字,主要体现在用户可以不关注资源的释放,因为智能指针会帮你完全管理资源的释放,它会保证无论程序逻辑怎么跑,正常执行或者产生异常,资源在到期的情况下,一定会进行释放。
C++11库里面,提供了带引用计数的智能指针和不带引用计数的智能指针,这篇文章主要介绍它们的原理和应用场景,包括auto_ptr,scoped_ptr,unique_ptr,shared_ptr,weak_ptr。
一个简陋的智能指针
template<typename T>
class CSmartPtr
{
public:
    CSmartPtr(T *ptr = nullptr)
        :mptr(ptr){}
    ~CSmartPtr()
    {
        delete mptr;
    }
private:
    T* mptr;
};
int main()
{
    int *p = new int;
    CSmartPtr<int> ptr1(new int);
    //定义了一个CSmartPtr<int>的对象,对象内托管这int类型的指针
    //由于这个对象定义在栈上,当函数结束栈帧消失,对象自动调用析构函数,
    //从而使对象托管的内存自动释放
    return 0;
}
可不可以将智能指针分配在堆内存上?
CSmartPtr<int>* ptr2 = new CSmartPtr<int>(new int); 这是没有意义的,因为 ptr2 没有被栈上的对象所托管,这就导致了必须手动delete。这与普通的裸指针没有什么区别,使用没必要
template<typename T>
class CSmartPtr
{
public:
    CSmartPtr(T *ptr = nullptr)
        :mptr(ptr){}
    T& operator*()
    //必须返回引用,若返回 T 类型,就相当于返回一个临时量 <==> 20 = 30 这显然是错误的
    {
        return *mptr;
    }
    T* operator->()
    {
        return mptr;
    }
    ~CSmartPtr()
    {
        delete mptr;
    }
private:
    T* mptr;
};
int main()
{
    int *p = new int;
    CSmartPtr<int> ptr1(new int);
    (*ptr1) = 20;
    class Test
    {
    public:
        void test(){cout<<"Test()"<<endl;}
    };
    CSmartPtr<Test> ptr3 = new(Test);
    ptr3->test();  // 相当于 (ptr3.operator->())->test()
    return 0;
}
不带引用计数的智能指针
先来看段代码
int main()
{
    CSmartPtr<int> ptr1(new int);
    CSmartPtr<int> ptr2(ptr1);        //用ptr1拷贝构造ptr2(浅拷贝)
    return 0;
}
这会导致double free
解决方案,实现深拷贝
    CSmartPtr(const CSmartPtr<T> &another)
    {
        T* p = new T(*(another.mptr));
    }
但是,这不符合实际性 ,当用户执行CSmartPtr<int> ptr2(ptr1);时,会理所当然的认为 ptr1 和 ptr2 共同管理了同一块内存。可实际却是,两个指针分别指向不同的内存资源
auto_ptr拷贝构造的实现
我们来看看c++系统中是如何实现拷贝构造的,同时避免浅拷贝
//完成了对象的托管,并将原来的指针制空
auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) { }
release() throw()
{
	element_type* __tmp = _M_ptr;
	_M_ptr = 0;
	return __tmp;
}

这就导致了一旦发生拷贝构造,原来指针就无法使用的尬尴处境,这也是不推荐使用auto_ptr的原因
推荐使用unique_ptr
两种初始化方式:
new 和 make_unique
	unique_ptr<Test> ptr(new Test(10));
	unique_ptr<Test> ptr2 = make_unique<Test>(20);
源码将拷贝构造与拷贝赋值禁用了
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
但是提供了右值参数的拷贝构造与赋值重载
unique_ptr& operator=(unique_ptr&&) = default;
unique_ptr(unique_ptr&&) = default;
使用unique_ptr
int main()
{
    unique_ptr<int> ptr1(new int);
    unique_ptr<int> ptr2(std::move(ptr1));   //使用户感知到自己将ptr1的资源转移到ptr2上
    return 0;
}
使用带引用字数的智能指针share_ptr
带引用计数的智能指针可以管理同一个资源
智能指针给每一个对象资源匹配一个引用计数
当智能指针在引用资源的时候引用计数加一
智能指针不在引用资源的时候引用计数减一当引用计数为零时,资源析构
初始化:
new关键字share_ptr<int> ptr(new int(10));
share_make share_ptr<int> ptr(share_make<int>(10)) 若为类类型,括号内填写构造参数
对数组的托管:share_ptr<Test[]> ptr(Test[10]()); 
常用方法:
use_count():返回智能指针所指资源的引用计数
	shared_ptr<Test> ptr(make_shared<Test>(10));
	cout << ptr.use_count() << endl;        //1
	shared_ptr<Test> ptr2 = ptr;
	cout << ptr.use_count() << "  " << ptr2.use_count() << endl; //2  2
reset():无参数时,暂停当前智能指针对资源的托管
	shared_ptr<Test> ptr(make_shared<Test>(10));
	cout << ptr.use_count() << endl;        //1
	shared_ptr<Test> ptr2 = ptr;
	cout << ptr.use_count() << "  " << ptr2.use_count() << endl; //2  2
	ptr.reset();
	cout << ptr.use_count() << "  " << ptr2.use_count() << endl; //0  1
        //当一块资源的引用计数为0时,自动释放堆内存
get():返回托管资源的指针
unique():判断该智能指针是否独自占用资源,是返回true否则返回false
	shared_ptr<Test> ptr(make_shared<Test>(10));
	cout << ptr.unique() << endl;                         //1
	shared_ptr<Test> ptr2 = ptr;
	cout << ptr.unique() <<"  "<<ptr2.unique() << endl;   //0  0
	ptr.reset();
	cout << ptr.unique() << "  " << ptr2.unique() << endl;//0  1
模拟实现
template<typename T>
class RefCnt     //用于引用计数
{
public:
    explicit RefCnt(T* ptr = nullptr)
    {
        if(ptr != nullptr)
        {
            mptr = ptr;
            mcount = 1;
        }
    }
    void addRef()   //增加引用计数
    {
        mcount++;
    }
    int delRef()
    {
        mcount--;
    }
private:
    T* mptr;     //指向资源
    int mcount;  //这个资源被引用的次数
};
template<typename T>
class CSmartPtr
{
public:
    explicit CSmartPtr(T *ptr = nullptr)
        :mptr(ptr)
    {
        mpRefcnt = new RefCnt<T>(mptr);
    }
    CSmartPtr(const CSmartPtr<T> &src)
        :mptr(src.mptr),mpRefcnt(src.mpRefcnt)
    {
        if(src.mptr != nullptr)
        {
            mpRefcnt->addRef();
        }
    }
    CSmartPtr& operator=(const CSmartPtr<T>& src)
    {
        if((&src = this))
        {
            return *this;
        }
        if(mpRefcnt->delRef() == 0)
        {
            delete mpRefcnt->mptr;
        }
        mptr = src.mptr;
        mpRefcnt = src.mpRefcnt;
        mpRefcnt->addRef();
        return *this;
    }
    T& operator*()
    //必须返回引用,若返回 T 类型,就相当于返回一个临时量 <==> 20 = 30 这显然是错误的
    {
        return *mptr;
    }
    T* operator->()
    {
        return mptr;
    }
    ~CSmartPtr()
    {
        if(mpRefcnt->delRef() == 0)
        delete mptr;
    }
private:
    T* mptr;                    //指向资源的指针
    RefCnt<T>* mpRefcnt;        //指向资源引用计数的指针
};
int main()
{
    CSmartPtr<int> ptr1(new int);
    CSmartPtr<int> ptr2(ptr1);
}
share_ptr的交叉引用问题
强智能指针:可以改变资源的引用计数 share_ptr
弱智能指针:不会改变资源的引用计数 weak_ptr。弱智能指针只是一个观察者,无法直接访问资源,没有重载 * 与 ->
class B;
class A
{
public:
    A(){cout<<"A()"<<endl;}
    ~A(){cout<<"~A()"<<endl;}
    shared_ptr<B> _ptrb;
};
class B
{
public:
    B(){cout<<"B()"<<endl;}
    ~B(){cout<<"~B()"<<endl;}
    shared_ptr<A> _ptra;
};
int main() {
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());
    pa->_ptrb = pb;  //使得资源pb的引用计数加一
    pb->_ptra = pa;  //使得资源pa的引用计数加一
    cout<<pa.use_count()<<endl;  //use_count返回指向资源的引用计数
    cout<<pb.use_count()<<endl;
    return 0;
}
输出:
A()
B()
2
2
强智能指针的交叉引用(循环引用)造成了new出来的资源无法释放

解决方法:
定义对象时使用强智能指针,引用对象的时候使用弱智能指针。即将类A B内智能指针改为weak_ptr即可。
但是,弱智能指针无法访问资源,没有重载 * 与 -> ,这就需要提升了
class B;
class A
{
public:
    A(){cout<<"A()"<<endl;}
    ~A(){cout<<"~A()"<<endl;}
    void test()
    {
        cout<<"这是一个非常好用的方法"<<endl;
    }
    weak_ptr<B> _ptrb;
};
class B
{
public:
    B(){cout<<"B()"<<endl;}
    ~B(){cout<<"~B()"<<endl;}
    void func()
    {
        //_ptra->test();  无法使用
        shared_ptr<A> ps = _ptra.lock();
        //将weak_ptr提升为share_ptr。若_ptra指向资源的引用资源计数为0则返回nullptr
        //否则返回share_ptr<A>的对象,当然这会使对象的引用计数加一
        if(ps != nullptr)
        {
            ps->test();
        }
    }
    weak_ptr<A> _ptra;
};
int main() {
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());
    pa->_ptrb = pb;  //由于时weak_ptr故不会增加引用计数
    pb->_ptra = pa;
    cout<<pa.use_count()<<endl;  //use_count返回指向资源的引用计数
    cout<<pb.use_count()<<endl;
    return 0;
}
多线程访问共享对象的线程安全问题
先来看一段代码
class A
{
public:
    A(){cout<<"A()"<<endl;}
    ~A(){cout<<"~A()"<<endl;}
    void testA(){cout<<"非常好用的方法"<<endl;}
};
void handler01(A* q)
{
    std::this_thread::sleep_for(std::chrono::seconds(2));
    q->testA();
}
int main()
{
    cout<<"main thread  :"<<std::this_thread::get_id()<<endl;
    A *p = new A();
    thread t1(handler01,p);
    //std::this_thread::sleep_for(std::chrono::seconds(3));
    delete p;
    t1.join();
    
    return 0;
}
输出顺序:
main thread  :1
A()
~A()
非常好用的方法
显然这是不正确的,在线程t1中 q->testA()时,传入的A类型的对象q已经析构了。这种访问是不安全的
解决方案,增加引用计数用于探测q访问的对象是否存活。这就联想到了强弱智能指针了
class A
{
public:
    A(){cout<<"A()"<<endl;}
    ~A(){cout<<"~A()"<<endl;}
    void testA(){cout<<"非常好用的方法"<<endl;}
};
void handler01(weak_ptr<A> pw)
{
    std::this_thread::sleep_for(std::chrono::seconds(2));
    shared_ptr<A> t = pw.lock();
    if(t != nullptr) {
        t->testA();
        cout<<"ptr is ptr"<<endl;
    }
    else
        cout<<"ptr is nullptr"<<endl;
}
int main()
{
    {
        shared_ptr<A> p(new A);
        thread t1(handler01, weak_ptr<A>(p));
        //Zstd::this_thread::sleep_for(std::chrono::seconds(3));
        t1.detach();
    }
    return 0;
}
智能指针定义的删除器deletor
智能指针:能保证资源绝对释放,系统中默认是使用 delete 用于资源的释放。但是并不是所有的资源都是通过delete来释放的(比如数组要通过 delete[] 文件要通过 close ) ,这就需要我们来自定义删除器来指导智能指针删除所引用的内容
unique_ptr的定义
template <typename _Tp, typename _Dp = default_delete<_Tp>>
    class unique_ptr
模板拥有两个参数_TP用于指定类型,_DP用于指定删除方法
解决方法
方法一:
自定义删除类
template<typename T>
class deletorInt  //用于删除int数组对象
{
public:
    void operator()(T* ptr)   
    {
        cout<<"void operator()(T* ptr)"<<endl;
        delete[](ptr);
    }
};
template<typename T>
class deletorFile    //用于删除文案对象
{
public:
    void operator()(T *ptr)
    {
        fclose(ptr);
    }
};
int main()
{
    unique_ptr<int,deletorInt<int>> ptr1(new int[10]);
    return 0;
}
这种方法并不好,因为我们定义了一个模板,但是这个模板仅仅使用在智能指针的定义时,这有些浪费
方法二
使用ambda表达式 与 function表达式
int main()
{
    unique_ptr<int> p(new int);
    unique_ptr<int,function<void(int*)>> ptr1(new int[100]
            ,[](int *pa)->void{cout<<"call lambda";delete[] pa;}
            );
    return 0;
}
让我们来看看unique_ptr的源码吧
类的定义
  template <typename _Tp, typename _Dp = default_delete<_Tp>>
    class unique_ptr
别名
using deleter_type  = _Dp;
    
构造函数
template<typename _Del = deleter_type,
	       typename = _Require<is_copy_constructible<_Del>>>
	unique_ptr(pointer __p, const deleter_type& __d) noexcept
	: _M_t(__p, __d) { }
unique_ptr 可以转换为 share_ptr,但是
share_ptr 不可以转换为 unique_ptr
void func(unique_ptr<int> unique)  
{
	shared_ptr<int> share(move(unique));//完成unique_ptr到share_ptr的转换
	cout << share.use_count() << endl;
}
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号