学习C语言的人一直在接触裸指针,裸指针有很多不好用的地方(对于本菜鸡来讲),比如有以下原因:
- 资源忘记释放,导致资源泄露
- 同一资源释放多次,程序崩溃
智能指针的出现就很好的解决了上面的问题。
C++11库里面,提供了带引用计数的智能指针 和 不带引用计数的智能指针,这篇文章主要介绍他们的原理和应用和场景,包括auto_ptr, scoped_ptr, unique_ptr, shared_ptr, weak_ptr。
为了更好理解智能指针的原理,我们最好自己实现一个简单地智能指针窥探一下基本原理。利用栈上对象出作用于会自动析构这一特点,把资源释放的代码放到析构函数中执行。
templateclass CSmartPtr { public: CSmartPtr(T *ptr = nullptr) : mptr(ptr) {} ~CSmartPtr() { delete mptr; } private: T *mptr; }; int main() { CSmartPtr ptr(new int); return 0; }
1. 智能指针体现在把裸指针进行了一次面向对象的封装,在构造函数中初始化地址,析构函数中负责释放资源。
2. 利用栈上对象出作用于自动析构这一特点,保证了资源的释放。
有个面试问题问“能不能在对象定义智能指针?”,比如*CSmartPtr p = new CSmartPtr(new int);
p虽然是智能指针类型,但是实质上还是一个裸指针,因此p还是需要进行手动delete,又回到了裸指针面临的问题。
当然要做到智能指针和裸指针使用相似,需要重载 ** * 和 -> **函数
#includeusing namespace std; template class CSmartPtr { public: CSmartPtr(T *ptr = nullptr) : mptr(ptr) {} ~CSmartPtr() { delete mptr; } T &operator*() { return *mptr; } const T &operator*() const { return *mptr; } T &operator->() { return mptr; } const T &operator->() const { return mptr; } private: T *mptr; }; int main() { CSmartPtr ptr(new int); *ptr = 20; cout << *ptr << endl; return 0; }
上面的使用方法已经和普通的智能指针很想死了,但还是有着很大的问题,像下面:
int main()
{
CSmartPtr ptr1(new int);
CSmartPtr ptr2(ptr1);
return 0;
}
这个一运行直接崩溃,问题出在默认的的构造函数做的是浅拷贝,两个指针都持有一个new int资源,ptr2先释放,ptr1再重复释放资源,因此导致崩溃。
现在看看C++库中提供的智能指针的怎么解决的。
不带引用计数的智能指针- auto_ptr
先看一下主要源码
templateclass auto_ptr { // wrap an object pointer to ensure destruction public: typedef _Ty element_type; explicit auto_ptr(_Ty * _Ptr = nullptr) noexcept : _Myptr(_Ptr) { // construct from object pointer } auto_ptr(auto_ptr& _Right) noexcept : _Myptr(_Right.release()) { // construct by assuming pointer from _Right auto_ptr } _Ty * release() noexcept { // return wrapped pointer and give up ownership _Ty * _Tmp = _Myptr; _Myptr = nullptr; return (_Tmp); } private: _Ty * _Myptr; // the wrapped object pointer };
从源码可以看到,只有最后一个auto_ptr智能指针持有资源,原来的auto_ptr都被赋nullptr了
int main()
{
auto_ptr p1(new int);
auto_ptr p2 = p1;
*p1 = 10;
return 0;
}
这里可以注意到p1已经是个空指针了,访问空指针的指向是一个很危险的操作。
上面的程序,如果不了解auto_ptr很容易出现严重的问题。
现在考虑一个问题,auto_ptr能不能使用在容器当中?
int main()
{
vector> vec;
vec.push_back(auto_ptr(new int(10)));
vec.push_back(auto_ptr(new int(20)));
vec.push_back(auto_ptr(new int(30)));
//打印出10
cout << *vec[0] << endl;
vector> vec2 = vec;
}
因此,不要再容易中荣勇auto_ptr,C++最好不用auto_ptr,除非应用场景非常简单!
【总结】:auto_ptr处理了浅拷贝的问题,把前面的指针都置为nullptr
- scoped_ptr
浏览下scoped_ptr源码
templateclass scoped_ptr // noncopyable { private: T * px; scoped_ptr(scoped_ptr const &); scoped_ptr & operator=(scoped_ptr const &); typedef scoped_ptr this_type; void operator==( scoped_ptr const& ) const; void operator!=( scoped_ptr const& ) const; public: typedef T element_type; explicit scoped_ptr( T * p = 0 ): px( p ) // never throws { #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) boost::sp_scalar_constructor_hook( px ); #endif } #ifndef BOOST_NO_AUTO_PTR explicit scoped_ptr( std::auto_ptr p ) BOOST_NOEXCEPT : px( p.release() ) { #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) boost::sp_scalar_constructor_hook( px ); #endif } #endif ~scoped_ptr() // never throws { #if defined(BOOST_SP_ENABLE_DEBUG_HOOKS) boost::sp_scalar_destructor_hook( px ); #endif boost::checked_delete( px ); } };
私有化拷贝构造函数和赋值函数,这样scoped_ptr的智能指针对象就不支持这两种操作,从根本上杜绝浅拷贝的发生。因此,scoped_ptr也不能用在容器中,容器相互拷贝或者复制,就会引起scoped_ptr的拷贝构造和赋值,编译错误。
【总结】auto_ptr可以任意转移资源的所有权,而scoped_ptr不会转移所有权(拷贝构造和赋值被禁止)
- unique_ptr
template// = default_delete<_Ty> class unique_ptr : public _Unique_ptr_base<_Ty, _Dx> { // non-copyable pointer to an object public: typedef _Unique_ptr_base<_Ty, _Dx> _Mybase; typedef typename _Mybase::pointer pointer; typedef _Ty element_type; typedef _Dx deleter_type; unique_ptr(unique_ptr&& _Right) noexcept : _Mybase(_Right.release(), _STD forward<_Dx>(_Right.get_deleter())) { // construct by moving _Right } unique_ptr& operator=(unique_ptr&& _Right) noexcept { // assign by moving _Right if (this != _STD addressof(_Right)) { // different, do the move reset(_Right.release()); this->get_deleter() = _STD forward<_Dx>(_Right.get_deleter()); } return (*this); } void swap(unique_ptr& _Right) noexcept { // swap elements _Swap_adl(this->_Myptr(), _Right._Myptr()); _Swap_adl(this->get_deleter(), _Right.get_deleter()); } ~unique_ptr() noexcept { // destroy the object if (get() != pointer()) { this->get_deleter()(get()); } } _NODISCARD pointer operator->() const noexcept { // return pointer to class object return (this->_Myptr()); } _NODISCARD pointer get() const noexcept { // return pointer to object return (this->_Myptr()); } explicit operator bool() const noexcept { // test for non-null pointer return (get() != pointer()); } pointer release() noexcept { // yield ownership of pointer pointer _Ans = get(); this->_Myptr() = pointer(); return (_Ans); } void reset(pointer _Ptr = pointer()) noexcept { // establish new pointer pointer _Old = get(); this->_Myptr() = _Ptr; if (_Old != pointer()) { this->get_deleter()(_Old); } } unique_ptr(const unique_ptr&) = delete; unique_ptr& operator=(const unique_ptr&) = delete; };
从上面看到,unique_ptr有一点和scoped_ptr做的一样,就是去掉了拷贝构造函数和operator=赋值重载函数,禁止用户对unique_ptr进行显示的拷贝构造和赋值,防止智能指针浅拷贝问题的发生。
但是unique_ptr提供了带右值引用参数的拷贝构造和赋值,也就是说,unique_ptr智能指针可以通过右值引用进行拷贝构造和赋值操作,或者在产生unique_ptr临时对象的地方,如把unique_ptr作为函数的返回值时,示例代码如下:
//示例1 unique_ptrptr(new int); unique_ptr ptr2 = move(ptr);//使用了右值引用的拷贝构造 ptr2 = move(ptr);//使用了右值引用的operator=重载函数 }
//示例2 unique_ptrtest_uniqueptr() { unique_ptr ptr1(new int); return ptr1; } int main() { //调用上面函数,return ptr1,此时调用右值引用的拷贝构造 unique_ptr ptr = test_uniqueptr();//延长了ptr1的生命周期 return 0; }
unique_ptr还提供了reset重置资源,swap交换资源等函数。
可以看到,unique_ptr从名字就可以看出来,最终也是只能有一个该智能指针引用资源,因此建议在使用不带引用计数的智能指针时,可以优先选择unique_ptr智能指针。
当允许多个智能指针指向同一个资源的时候,每一个智能指针都会给资源的引用计数+1,析构-1,最后一个智能指针把资源的引用计数从1减到0,才真正释放了这个资源。
对整数的++或者–操作并不是线程安全的,因此share_ptr和weak_ptr底层的引用计数已经通过CAS操作。
private:
element_type * _Ptr{nullptr};
_Ref_count_base * _Rep{nullptr};
因此,shared_ptr智能指针的资源引用计数器在内存的heap堆上。shared_ptr一般被称作强智能指针,weak_ptr被称作弱智能指针,它们有下边两个非常重要的应用场景需要注意。
智能指针的交叉引用(循环引用)问题看下面示例
#include#include using namespace std; class B; // 前置声明类B class A { public: A() { cout << "A()" << endl; } ~A() { cout << "~A()" << endl; } shared_ptr _ptrb; // 指向B对象的智能指针 }; class B { public: B() { cout << "B()" << endl; } ~B() { cout << "~B()" << endl; } shared_ptr _ptra; // 指向A对象的智能指针 }; int main() { shared_ptr ptra(new A());// ptra指向A对象,A的引用计数为1 shared_ptr ptrb(new B());// ptrb指向B对象,B的引用计数为1 ptra->_ptrb = ptrb;// A对象的成员变量_ptrb也指向B对象,B的引用计数为2 ptrb->_ptra = ptra;// B对象的成员变量_ptra也指向A对象,A的引用计数为2 cout << ptra.use_count() << endl; // 打印A的引用计数结果:2 cout << ptrb.use_count() << endl; // 打印B的引用计数结果:2 return 0; }
可以看到,A和B对象并没有进行析构,通过上面的代码示例,能够看出来“交叉引用”的问题所在,就是对象无法析构,资源无法释放,那怎么解决这个问题呢?请注意强弱智能指针的一个重要应用规则:定义对象时,用强智能指针shared_ptr,在其它地方引用对象时,使用弱智能指针weak_ptr。
弱智能指针weak_ptr区别于shared_ptr之处在于:
- weak_ptr不会改变资源的引用计数,只是一个观察者的角色,通过观察shared_ptr来判定资源是否存在
- weak_ptr持有的引用计数,不是资源的引用计数,而是同一个资源的观察者的计数
- weak_ptr没有提供常用的指针操作,无法直接访问资源,需要先通过lock方法提升为shared_ptr强智能指针,才能访问资源
那么上面的代码怎么修改,**也就是如何解决带引用计数的智能指针的交叉引用问题,**代码如下:
#include自定义删除器#include using namespace std; class B; // 前置声明类B class A { public: A() { cout << "A()" << endl; } ~A() { cout << "~A()" << endl; } weak_ptr _ptrb; // 指向B对象的弱智能指针。引用对象时,用弱智能指针 }; class B { public: B() { cout << "B()" << endl; } ~B() { cout << "~B()" << endl; } weak_ptr _ptra; // 指向A对象的弱智能指针。引用对象时,用弱智能指针 }; int main() { // 定义对象时,用强智能指针 shared_ptr ptra(new A());// ptra指向A对象,A的引用计数为1 shared_ptr ptrb(new B());// ptrb指向B对象,B的引用计数为1 // A对象的成员变量_ptrb也指向B对象,B的引用计数为1,因为是弱智能指针,引用计数没有改变 ptra->_ptrb = ptrb; // B对象的成员变量_ptra也指向A对象,A的引用计数为1,因为是弱智能指针,引用计数没有改变 ptrb->_ptra = ptra; cout << ptra.use_count() << endl; // 打印结果:1 cout << ptrb.use_count() << endl; // 打印结果:1 return 0; }
~unique_ptr() noexcept
{ // destroy the object
if (get() != pointer())
{
this->get_deleter()(get()); // 这里获取底层的删除器,进行函数对象的调用
}
}
从unique_ptr的析构函数可以看到,如果要实现一个自定义的删除器,实际上就是定义一个函数对象而已,示例代码如下:
class FileDeleter
{
public:
// 删除器负责删除资源的函数
void operator()(FILE *pf)
{
fclose(pf);
}
};
int main()
{
// 由于用智能指针管理文件资源,因此传入自定义的删除器类型FileDeleter
unique_ptr filePtr(fopen("data.txt", "w"));
return 0;
}
当然这种方式需要定义额外的函数对象类型,不推荐,可以用C++11提供的函数对象function和lambda表达式更好的处理自定义删除器,代码如下:
int main()
{
// 自定义智能指针删除器,关闭文件资源
unique_ptr>
filePtr(fopen("data.txt", "w"), [](FILE *pf)->void{fclose(pf);});
// 自定义智能指针删除器,释放数组资源
unique_ptr>
arrayPtr(new int[100], [](int *ptr)->void {delete[]ptr; });
return 0;
}
https://blog.csdn.net/QIANGWEIYUAN/article/details/88562935?spm=1001.2014.3001.5501



