目录智能指针的原理RaiI智能指针的原理auto_ptr1.auto_ptr的使用及问题unique_ptrshared_ptrshared_ptr的循环引用智能指针的原理 RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。 借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
我们使用RAII的思想设计SmartPtr类:
template <class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{}
~SmartPtr()
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
}
private:
T* _ptr;
};
上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容 ,因此:SmartPtr模板类中还得需要将* 、->重载下,才可让其像指针一样去使用。
template <class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~SmartPtr()
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
}
private:
T* _ptr;
};
智能指针使用:
总结智能指针的原理:
auto_ptr的头文件#include<memory>
auto_ptr的使用:
为什么此时访问sp的成员时会报错呢?我们来看看它们的地址。
我们发现在拷贝构造之后,sp管理的地址为空,而sp1管理的地址是之前sp所管理的地址,管理权发生了转移。那么上面所说的报错也很容易想通,因为sp管理的地址为空,不能进行访问。
auto_ptr的问题:当对象拷贝或者赋值后,管理权进行转移,造成前面的对象悬空。auto_ptr问题是非常明显的,所以实际中很多公司明确规定了不能使用auto_ptr。
auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份AutoPtr来了解它的原理:
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{}
//拷贝:管理权转移
AutoPtr(AutoPtr<T> &sp)
:_ptr(sp._ptr)
{
sp._ptr = nullptr;
}
//赋值:管理权转移
AutoPtr& operator=(AutoPtr<T> &sp)
{
if (this != &sp)
{
if (_ptr)
delete _ptr;
_ptr = sp._ptr;
sp._ptr = nullptr;
}
return *this;
}
~AutoPtr()
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
private:
T* _ptr;
};
为了解决拷贝或者赋值时管理权转移的问题,出现了unique_ptr。
unique_ptr解决问题的方式非常粗暴:防拷贝,也就是不让赋值和拷贝
unique_ptr的使用:
unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原理:
template<class T>
class UniquePtr
{
public:
UniquePtr(T* ptr)
:_ptr(ptr)
{}
// c++11防拷贝的方式:delete
UniquePtr(const UniquePtr<T> &) = delete;
UniquePtr& operator=(const UniquePtr<T>&) = delete;
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
~UniquePtr()
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
}
private:
//C++98防拷贝的方式:只声明不实现+声明成私有
//UniquePtr(UniquePtr<T> const &);
//UniquePtr& operator=(UniquePtr<T> const &);
T* _ptr;
};
c++11中提供更靠谱的并且支持拷贝的shared_ptr
shared_ptr的使用
shared_ptr中拷贝与赋值都是没有问题的。
shared_ptr的原理
shared_ptr中成员函数:use_count(对象数据的引用计数)
示例:
示例详解:
利用引用计数简单的实现SharedPtr,了解原理:
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr)
:_ptr(ptr)
,_count(new int(1))
{}
SharedPtr(const SharedPtr<T> &sp)
:_ptr(sp._ptr)
,_count(sp._count)
{
//计数器累加
++(*_count);
}
SharedPtr& operator=(const SharedPtr<T> &sp)
{
//判断管理的是否是同一份资源
if (_ptr != sp._ptr)
{
//计数-1,判断之前管理的资源是否需要释放
if ((--(*_count)) == 0)
{
delete _ptr;
delete _count;
}
_ptr = sp._ptr;
_count = sp._count;
//计数器累加
++(*_count);
}
return *this;
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
~SharedPtr()
{
if (--(*_count) == 0)
{
delete _ptr;
delete _count;
_ptr = nullptr;
_count = nullptr;
}
}
private:
T* _ptr;
int* _count;//给每份资源开辟一个计数器
};
但是还存在一个线程安全的问题:
这里我们通过加锁来解决线程安全问题:
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr)
:_ptr(ptr)
,_count(new int(1))
,_mutex(new mutex)
{}
SharedPtr(const SharedPtr<T> &sp)
:_ptr(sp._ptr)
,_count(sp._count)
,_mutex(sp._mutex)
{
//计数器累加
AddCount();
}
SharedPtr& operator=(const SharedPtr<T> &sp)
{
//判断管理的是否是同一份资源
if (_ptr != sp._ptr)
{
//计数-1,判断之前管理的资源是否需要释放
if (SubCount() == 0)
{
delete _ptr;
delete _count;
delete _mutex;
}
_ptr = sp._ptr;
_count = sp._count;
_mutex = sp._mutex;
//计数器累加
AddCount();
}
return *this;
}
//线程安全的累加器
int AddCount()
{
//加锁
_mutex->lock();
++(*_count);
_mutex->unlock();
return *_count;
}
int SubCount()
{
_mutex->lock();
--(*_count);
_mutex->unlock();
return *_count;
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
~SharedPtr()
{
if (SubCount() == 0)
{
delete _ptr;
delete _count;
delete _mutex;
_ptr = nullptr;
_count = nullptr;
_mutex = nullptr;
}
}
private:
T* _ptr;
int* _count;//给每份资源开辟一个计数器
mutex* _mutex; //每一份资源有一个独立的锁
};
循环引用的场景:
struct Listnode
{
int _data;
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
~ListNode() { cout << "~ListNode()" << endl; }
};
void test()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
node1->_next = node2;
node2->_prev = node1;
}
node1和node2两个智能指针对象指向两个节点,两个节点的引用计数都是1。node1->next指向node2,node2->prev指向node1,两个节点的引用计数都变成2。程序运行完之后,析构node1和node2,node1和node2所指向的节点引用计数分别减1,但是node1->next指向下面节点,node2->prev指向上面节点,此时,两个节点的引用计数都为1,所以两个节点不能析构。
引用计数为0时,如果要析构node1节点,就先要去析构node1中的自定义结构,然后再析构node1。也就是说node1->next析构了,node2就释放了;node2->prev析构了,node1就释放了。但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。
解决方案:
在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
原理就是,node1->_next = node2和node2->_prev = node1时weak_ptr的_next和_prev不会增加node1和node2的引用计数。
weak_ptr最大作用就是解决shared_ptr的循环引用
struct ListNode
{
int _data;
weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;
~ListNode() { cout << "~ListNode()" << endl; }
};
void test()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
node1->_next = node2;
node2->_prev = node1;
}
注意:
weak_ptr不能单独使用,可以用shared_ptr创建
//weak_ptr错误使用
weak_ptr<ListNode> node1(new ListNode);
//weak_ptr正确使用
shared_ptr<ListNode> node2(new ListNode);
weak_ptr<ListNode> node3(node2);
到此这篇关于C++11 智能指针的具体使用的文章就介绍到这了,更多相关C++11 智能指针内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
--结束END--
本文标题: C++11 智能指针的具体使用
本文链接: https://lsjlt.com/news/133443.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-03-01
2024-03-01
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0