写在前面
对在C++中new出来的堆对象,在程序结束前必然需要我们手动进行delete,否则会造成内存泄漏。C++11提出的智能指针就是利用类对象在程序结束前自动调用析构函数的方式来自动管理内催空间。因此,智能指针是一个类。C++98中有auto_ptr(书院弃徒,垃圾),但由于本身的缺陷,已经被C++11弃用。C++11中,三个更为优秀的智能指针横空出世,惊才绝艳。
想要绝世之才,必然要从弟弟开始做起
智能指针中的弟弟,非auto_ptr不可。虽然后来的哥哥们比较牛逼,但是万变不离其宗,原型都是auto弟弟。所谓智能指针,就是对一个堆对象再进行一层包装(像一个指针,但不是指针),将对象作为智能指针的私有成员,当智能指针析构时,将堆对象也一起释放。
template<class T>//模板类
class Auto_ptr {
public:
Auto_ptr(T* p) {
ptr = p;
}
Auto_ptr(Auto_ptr<T>& ap) {
ptr = ap.ptr;
ap.ptr = NULL;//剥夺之前的所有权
}
//运算符重载
//重载等号
Auto_ptr<T>& operator=(Auto_ptr<T>& ap) {
if (this != &ap) {
if (ptr) {
delete ptr;
}
this->ptr = ap.ptr;
ap.ptr = NULL;
}
return *this;
}
//智能指针,需要重载一个指针的运算符*
T& operator*() {
return *(this->ptr);
}
T* operator->() {
return ptr;
}
~Auto_ptr() {
if (ptr) {//避免依旧悬空的autoptr被delete
delete ptr;
}
cout << "auto deconstructor" << endl;
}
private:
T* ptr;
};
如上所示,将智能指针写成模板类。私有成员为传入的堆对象地址(必须)。当超出智能指针的作用范围时,自动调析构函数时我们将堆对象的空间也一并释放,实现了内存的自动管理。同时,为了让sp使用起来更像一个指针,重载了*和->运算符。注意拷贝构造函数,当我们完成了对象的拷贝时,为了实现auto的独占式,我们将源对象的指针置为null,导致悬空。在实际应用中,我们需要时刻记住智能指针谁复制给了谁,否则使用了悬空的智能指针无法访问到对应值。这就是auto带来的致命缺陷。
auto_ptr的晋级——unique_ptr
为了避免上述问题,C++11对于独占式拥有的智能指针提出了新的unique_ptr,即一个对象和其对应的资源同一时间智能被一个指针拥有。为了防止复制后悬空,直接禁用拷贝构造。
template<class T>
class Unique_ptr {
private:
T* ptr;
public:
Unique_ptr() {
ptr = nullptr;
}
Unique_ptr(T* p) {
ptr = p;
}
~Unique_ptr() {
if (ptr)
delete ptr;
}
//unique为了避免出现auto中对象指针释放后悬空的情况,暴力禁止拷贝构造
Unique_ptr(Unique_ptr<T>& up) = delete;
//重载
T& operator*() {
return *ptr;
}
T* operator->() {
return ptr;
}
};
智能指针得道成仙,共享式shared_ptr
C++11中引入写时拷贝和引用计数,实现了智能指针的升华。上述两种均是独占式,不符合像指针的要求,为了可以让多个“指针”指向同一个堆对象,共享是必然的。
shared_ptr采用引用计数的方法,记录当前内存资源被多少个智能指针引用。当新增一个sp,引用计数+1,反之-1,只有当引用计数为0时,才会销毁对象,释放资源。听上去很复杂,实际上呢,就是在sp类中增加一个计数值进行维护。
template<class T>
class Share_ptr {
private:
T* ptr;
int* count;//引用计数//为什么用指针变量?所有share公用一个计数地址
//每个shareptr对象都有一个自己的计数指针,但是大家指向同一个地址(同一个地球,同一片蓝天,家只有一个咦嘻嘻嘻)
public:
Share_ptr() {
ptr = nullptr;
count=new int(0);
}
//constructor
Share_ptr(T* p): ptr(p),count(new int(1)) {
return;
}
//copy
Share_ptr(const Share_ptr<T>& sp) {
this->ptr = sp.ptr;
this->count = sp.count;
++(*count);//引用计数增加,由于统一地址,大家一起增加
}
//de
~Share_ptr() {
if (--(*count) == 0) {
delete ptr;
delete count;
}
}
//运算符重载
bool operator!=(Share_ptr<T>& sp) {
if (ptr!=sp.ptr || count!=sp.count)
return true;
return false;
}
Share_ptr& operator=(Share_ptr<T>& sp) {
//拷贝两部分,资源指针,引用计数
//首先要判断是否有资源
if (--(*count) == 0) {
delete ptr;
delete count;//这里的作用,为何要delete?
//如果当前复制的目的指针非空,需要先进行清理
}
ptr = sp.ptr;
count = sp.count;//完成复制
++(*count);//计数增加
return *this;
}
T& operator*() {
return *ptr;
}
T* operator->() {
return ptr;
}
//返回当前指针数量
int howMany() {
return *count;
}
};
当然,共享式也不是没有任何问题。当存在两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。C++11中引入了weak_ptr来解决这个问题。它允许共享但不拥有某个对象,一旦最后一个拥有该对象的智能指针失去所有权后,weak_ptr会自动清空。