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号