更多内容点这里
C++学习 - 目录索引
1、为什么要有智能指针?
因为在代码中经常忘记释放动态开辟的内存资源,造成内存泄漏,并且在操作时要小心谨慎,谨防内存泄漏。因此产生了智能指针来动态的管理指针所指向的动态资源释放
1.1、追溯历史
- C++98中一开始的智能指针是auto_ptr,auto_ptr是一种管理权的转移操作,但是auto_ptr依然存在缺陷,好多人是不推荐使用这个的
- 在98到11这十几年时间中,产生了一个boost社区,里面的人为了弥补auto_ptr的缺陷,相继创造出来了《scoped_ptr/scoped_array:一种粗暴模式的防拷贝,推荐使用》,《shared_ptr/shared_array:在需要拷贝的地方增加了引用计数功能》,《weak_ptr:具有弱指针功能,主要配合解决shared_ptr循环引用》
- C++11出现了《unique_ptr/shared_ptr》,它是参考的boost社区开发的智能指针
1.2、auto_ptr
- 1、auto_ptr第一种实现方式
作用:是一种将权限转移的方式(即:一个指针将一块空间的管理权限交给另一个指针)
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{}
~AutoPtr()
{
printf("~ptr\n");
delete _ptr;
}
//ap2(ap1)
AutoPtr(AutoPtr<T>& ap)
{
_ptr = ap._ptr;
ap._ptr = NULL;
}
//ap2 = ap1
AutoPtr<T>& operator = (AutoPtr<T> &ap)
{
if (this != &ap)
{
delete _ptr;
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T& operator->()
{
return _ptr;
}
private:
T* _ptr;
};
void TestAutoPtr()
{
AutoPtr<int> ap1(new int(10));
AutoPtr<int>ap2(ap1);
//*ap1 = 100;//此时ap1已经被置为空了,此时给*ap1赋值就会出现问题
*ap2 = 200;
AutoPtr<int>ap3(new int(10));
ap3 = ap1;//此时ap3被置为NULL
ap3 = ap2;//此时ap2被置为NULL
}
- auto_ptr优点:实现智能管理指针指向动态资源的释放
auto_ptr缺点:就像拷贝构造函数和赋值运算符函数一样,比如拷贝构造函数,我们用ap2拷贝ap1,但是当拷贝完成后,发现ap1内部指针被置为空了,当我们再次调用ap1时,发现对象已经被释放了,所以程序就会崩溃。
auto_ptr的第二种实现方式
成员变量增加一个bool值,如果该对象对于bool值为true就去析构该对象
但是这种情况依然存在缺点:当ap1出了作用域后,调用析构函数将资源释放,但是因为ap1和ap2共用一块地址空间,此时再调用ap2发现就会出现问题,显然ap2已经成为野指针,造成内存泄漏。
1.3、scoped_ptr
作用:这个指针以一种简单粗暴的模式直接抛弃的auto_ptr的缺点,这个指针直接不允许你拷贝和赋值
实现这种机制的方法有二种:
- 1、将拷贝构造函数和赋值操作符函数声明为私有成员
- 2、只声明构造函数和赋值操作符函数,不定义
个人觉得将拷贝构造和赋值操作符声明为私有的成员更妥当一些。
1.3.1:代码实现
template<class T>
class ScopedPtr
{
public:
ScopedPtr(T* ptr)
:_ptr(ptr)
{}
~ScopedPtr()
{
cout << "~ScopedPtr()" << endl;
}
T& operator*()
{
return *_ptr;
}
T& operator->()
{
return _ptr;
}
private:
T* _ptr;
ScopedPtr(const ScopedPtr<T>& sp);
ScopedPtr<T>& operator = (const ScopedPtr<T> &sp);
};
void TestScopedPtr()
{
ScopedPtr<int>sp1(new int(3));
ScopedPtr<int>sp2(sp1);
ScopedPtr<int>sp3(new int(3));
sp3 = sp1;
}
用这种简单粗暴的方式防止你在外面调用这两种函数
1.4、shared_ptr
作用:像指针一样,但会记录有多少个shared_ptrs共同指向一个对象。这便是所谓的引用计数,一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。这在非环形数据结构中防止资源泄露很有帮助。这样指针可以共享一块内存,并且不用考虑内存泄漏问题。
优点:
- 多个指针指向同一个内存空间,释放后,不会造成内存泄漏
- 更加安全便捷的管理内存空间
- 可以安全的进行赋值操作符函数和拷贝构造含糊
- 多个指针共同管理一块空间,从而更加智能
1.4.1:代码实现
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr, int *pcount)
:_ptr(ptr)
, _pCount(pcount)
{
cout << "SharedPtr" << endl;
}
~SharedPtr()
{
if (--(*_pCount) == 0)
{
delete[] _ptr;
delete[] _pCount;
cout << "~SharedPtr" << endl;
_ptr = NULL;
_pCount = NULL;
}
}
SharedPtr(const SharedPtr<T> & sp)
{
_ptr = sp._ptr;
_pCount = sp._pCount;
++(*_pCount);
}
SharedPtr<T>& operator = (const SharedPtr<T>& sp)
{
if (this != &sp)
{
if (--(*_ptr) == 0)
{
delete _ptr;
delete _pCount;
}
_ptr = sp._ptr;
_pCount = sp._pCount;
++(*_pCount);
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T*_ptr;
int* _pCount;
};
缺点:
会产生循环引用的问题
那么问题来了,什么是循环引用?
1.4.2、循环引用
eg:
struct ListNode
{
SharedPtr<ListNode> _next;
SharedPtr<ListNode> _prev;
ListNode()
:_prev(NULL)
,_next(NULL)
{}
~ListNode()
{
cout<<"delete ListNode"<<endl;
}
};
void TestSharePtrCycle()
{
SharedPtr<ListNode> cur = new ListNode;
SharedPtr<ListNode> next = new ListNode;
cur->_next = next;
next->_prev = cur;
}
分析:
因此为了解决这个问题,引用了weak_ptr。
1.4.3、weak_ptr
作用:weak_ptr(弱指针),专门解决shared_ptr循环引用问题。只引用不计数。
代码演示:
template<class T>
class WeakPtr;
template<class T>
class SharedPtr
{
friend class WeakPtr<T>;
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _pCount(new int(1))
{
cout << "SharedPtr" << endl;
}
SharedPtr(const SharedPtr<T>& sp)
{
_ptr = sp._ptr;
_pCount = sp._pCount;
(*_pCount)++;
}
// sp1 = sp2;
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
{
if (this != &sp)
{
if (--(*_pCount) == 0)
{
delete _ptr;
delete _pCount;
}
_pCount = sp._pCount;
_ptr = sp._ptr;
(*_pCount)++;
}
return *this;
}
~SharedPtr()
{
//cout<<"~SharedPtr()"<<endl;
if (--(*_pCount) == 0)
{
if (_ptr)
{
printf("ptr:%p\n", _ptr);
delete _ptr;
}
delete _pCount;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int Count()
{
return *_pCount;
}
protected:
T* _ptr;
int* _pCount;
};
template<class T>
class WeakPtr
{
public:
WeakPtr(SharedPtr<T>& sp)//这里将sharedptr的对像传给他,匿名对象
:_ptr(sp._ptr) //这里将sharedptr的对象给weakptr(弱指针)进行初始化,此时弱指针指向的是sharedptr的对象
{}
WeakPtr()
:_ptr(NULL)
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T* _ptr;
};
struct ListNode
{
WeakPtr<ListNode> _next;
WeakPtr<ListNode> _prev;
~ListNode()
{
cout << "delete ListNode" << endl;
}
};
void TestSharePtrCycle()
{
SharedPtr<ListNode> cur = new ListNode;//使用弱指针weak_ptr
SharedPtr<ListNode> next = new ListNode;
//使用弱指针来负责cur->next 和next->prev的指向,使其引用计数不在加1
cur->_next = next;//weakptr接收的是sharedptr的对象,所以可以相互指向
next->_prev = cur;
cout << "cur:" << cur.Count() << endl;
cout << "next:" << next.Count() << endl;
}
因此:弱指针是指当指针指向原来空间时,引用计数不在进行加1,释放时可直接释放,因此解决了循环引用的问题,它不是为了管理指针,而是为了配合shared_ptr避免了一次引用计数。