1. 什么是智能指针
简而言之,为了更安全的使用指针。 实现方式简单来说,就是用一个模板类把一般的指针包装起来。用这个类来维护内部指针的释放操作。 std中有四种智能指针。
- auto_ptr(已弃用)
- unique_ptr
- shared_ptr
- weak_ptr
2. auto_ptr
比较简单的智能指针,实现逻辑如下代码。 实现的原则是:内存空间只能由一个指针所拥有。 存在的问题:拷贝和赋值后,原对象内部的指针会为空,有风险。
template<class T>
class autoPtr{
public:
autoPtr(T *a):m_ptr(a){
}
~autoPtr(){
if(m_ptr){
delete m_ptr;
m_ptr = nullptr;
}
}
autoPtr(autoPtr<T> &a):m_ptr(a.m_ptr) {
a.m_ptr = nullptr;
}
autoPtr& operator= (autoPtr<T> &a){
if(this != &a){
if(m_ptr) delete m_ptr;
m_ptr = a.m_ptr;
a.m_ptr = nullptr;
}
return *this;
}
T* operator->(){
return m_ptr;
}
T& operator* (){
return *m_ptr;
}
private:
T *m_ptr;
};
3. unique_ptr
是对auto_ptr的改进,实现逻辑如下代码。 实现的原则是:内存空间只能由一个指针所拥有,但是禁止了拷贝和赋值。
template<class T>
class uniquePtr{
public:
uniquePtr(const T*a):m_ptr(a) { };
~uniquePtr(){ if(m_ptr) delete m_ptr; }
T& operator *(){
return *m_ptr;
}
T* operator->(){
return m_ptr;
}
T* get(){ return m_ptr; }
private:
T *m_ptr;
uniquePtr(const uniquePtr<T> &a) { };
uniquePtr<T> & operator = (const uniquePtr<T> &a) { };
};
4. shared_ptr
实现逻辑如下代码。 实现的原则是:内存空间可以由多个指针所拥有,但是要控制内存释放的时机,这个时机由“引用计数 ”来实现。 存在为问题:会存在循环引用问题,导致内存无法正常释放。
template<class T>
class sharedPtr{
public:
sharedPtr(T *a):m_ptr(a), m_count(new int(1)) { };
~sharedPtr(){
if(--(*m_count) == 0){
delete m_ptr;
delete m_count;
m_ptr = nullptr;
m_count = nullptr;
}
}
T& operator *(){
return *m_ptr;
}
T* operator->(){
return m_ptr;
}
sharedPtr(const sharedPtr<T> &a):m_ptr(a.m_ptr),m_count(a.m_count) {
++(*m_count);
}
sharedPtr<T> & operator=(const sharedPtr<T> &a){
if(m_ptr != a.m_ptr){
if(--(*m_count) == 0){
delete m_ptr;
delete m_count;
m_ptr = nullptr;
m_count = nullptr;
}
m_ptr = a.m_ptr;
m_count = a.m_count;
++(*m_count);
}
return *this;
}
int use_count(){
return *m_count;
}
int * use_countPtr(){
return m_count;
}
T* get(){
return m_ptr;
}
private:
T *m_ptr;
int *m_count;
};
shared_ptr会产生循环引用 问题,如下代码
template<class T>
class sharedPtr{
public:
sharedPtr(T *a):m_ptr(a), m_count(new int(1)) { };
~sharedPtr(){
if(--(*m_count) == 0){
delete m_ptr;
delete m_count;
m_ptr = nullptr;
m_count = nullptr;
}
}
T& operator *(){
return *m_ptr;
}
T* operator->(){
return m_ptr;
}
sharedPtr(const sharedPtr<T> &a):m_ptr(a.m_ptr),m_count(a.m_count) {
++(*m_count);
}
sharedPtr<T> & operator=(const sharedPtr<T> &a){
if(m_ptr != a.m_ptr){
if(--(*m_count) == 0){
delete m_ptr;
delete m_count;
m_ptr = nullptr;
m_count = nullptr;
}
m_ptr = a.m_ptr;
m_count = a.m_count;
++(*m_count);
}
return *this;
}
int use_count(){
return *m_count;
}
int * use_countPtr(){
return m_count;
}
T* get(){
return m_ptr;
}
private:
T *m_ptr;
int *m_count;
};
int main() {
struct Node{
Node():pre(nullptr), next(nullptr) { }
int data;
sharedPtr<Node> pre;
sharedPtr<Node> next;
};
int *c1 = nullptr;
int *c2 = nullptr;
{
sharedPtr<Node> a(new Node);
sharedPtr<Node> b(new Node);
a->next = b;
b->pre = a;
c1 = a.use_countPtr();
c2 = b.use_countPtr();
cout << *c1 <<endl;
cout << *c2 <<endl;
}
cout << *c1 <<endl;
cout << *c2 <<endl;
return 0;
}
对象a和b本身为sharedPtr类型,内部各包含一个Node* 并且开辟了内存。为了方便后续描述,a中申请的这块内存称之为A_Node* ,b中的这块内存称之为B_Node* 。
a和b初始化时,内部的引用计数均为1 ,因为这个时候只有自己使用自己内部的这块内存空间。 当执行了
a->next = b;
b->pre = a;
这两行代码以后,a和b内部的引用技术均为2 。此时对于a 来说,不仅仅自己使用A_Node* ,同时b 中的一个sharedPtr<Node> 类型(与a同类型)的pre ,也使用A_Node* ,所以a 中的引用计数为2,b中pre内部的引用计数也为2。
对于b来说,同理,不仅仅自己使用B_Node* ,同时a 中的一个sharedPtr<Node> 类型(与b同类型)的next ,也使用B_Node* ,所以b 中的引用计数为2,a中next内部的引用计数也为2。
重点来了,当程序执行到花括号 外边的时候,a和b两个对象的生命周期到了,要执行各自的析构函数。但是!执行a的析构函数时,由于引用计数为2,减去1之后不为0,所以,不会释放a内部的m_ptr和m_count ,只不过把m_count 变为了1,由于引用计数机制的存在,此时b中的pre,这个智能指针内部的引用计数也会变为1。总结一下,就是a这个对象被析构了,但是 A_Node* 没释放。
同理,执行b的析构函数时,由于引用计数为2,减去1之后不为0,所以,不会释放b内部的m_ptr和m_count ,只不过把m_count 变为了1,由于引用计数机制的存在,此时a中的next,这个智能指针内部的引用计数也会变为1。总结一下,就是b这个对象被析构了,但是 B_Node* 没释放。
经过了a和b两个对象的析构函数,现在的情况是怎么样的?
A_Node* 中的next指向B_Node* 。 B_Node* 中的pre指向A_Node* 。
此时呢,形成了环。
在没有人为干预的情况下,想释放A_Node* ,就必须先释放 B_Node* 。
因为一旦B_Node* 释放,会触发B_Node* 内部pre指针的析构函数,因此这时pre内部的引用计数为1,减1之后变为0,所以,可以释放掉pre所指向的内存空间,也就是会释放掉A_Node* ,然后接着会触发A_Node* 内部next指针的析构函数,同理,next指针会把自己指向的B_Node* 给释放掉,这样,A_Node*和B_Node* 这两块被a和b遗留下来的内存空间就可以正常被释放。
但是!!!问题来了,想释放B_Node* ,就必须先释放 A_Node* 。
二者互相等待,都等待对方先释放,这个就是循环引用 。
解决办法 把Node中的两个shared_ptr 换成weak_ptr 即可,这样,a和b的引用计数均为1,可以正常是释放。
5. weak_ptr
是对shared_ptr的修改,实现逻辑如下代码。 实现的原则是:允许一块内存由多个指针拥有,与shared_ptr不同的是,不使用引用计数。 存在的问题:适用性不如shared_ptr广,因为一个weak_ptr释放的时候,就会把自己指向的区域释放掉,可能会重复释放。
template<class T>
class weakPtr{
public:
weakPtr(T *a):m_ptr(a){ }
~weakPtr(){
if(m_ptr) {
delete m_ptr;
m_ptr = nullptr;
}
}
weakPtr<T> & operator= (const weakPtr<T> &a){
if(m_ptr) delete m_ptr;
m_ptr = a.m_ptr;
return *this;
}
weakPtr<T> & operator= (T *a){
m_ptr = a;
return *this;
}
T& operator *(){
return *m_ptr;
}
T* operator->(){
return m_ptr;
}
private:
T *m_ptr;
};
|