一、什么是智能指针
在讲智能指针之前我们必须先知道什么是智能指针,智能指针是一个类,是对普通指针的一个封装使得智能指针对象具有普通指针类型一样的操作。
智能指针是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄漏。它的具体通用的技术就是实现引用计数。(这个下面会讲)
智能指针是将一个计数器与类指向的对象相关连,引用计数就是跟踪该类有多少个对象共享这一个指针。每次创建类新的对象的时候,初始化指针将引用计数置为1,当对象作为另一对象的副本而创建的时候,拷贝构造函数拷贝指针并增加与之对应的引用计数。对一个对象进行赋值的时候,赋值操作符减少左操作数所指对象的引用计数(引用计数为0就会删除对象),并增加右操作数的所指对象的引用计数。调析构函数的时候,构造函数减少引用计数,引用计数减为0的时候,就删除基础的对象。
智能指针还有其他的功能,其中比较有用的就是自动销毁。这是利用找对象的有限作用域以及临时对象(有限作用域实现)析构函数来释放内存。
二、什么是引用计数
在智能指针中如何让指针知道其他指针的存在呢?所以就出现了引用计数。
在运用计数中,每一个对象负责维护对象所有引用的计数值。当一个新的引用指向对象的时候,引用计数器就自增;当去掉一个引用计数的时候,引用计数器就自减。引用计数为零的时候就会释放所占有的资源。
引用计数的使用常有两个目的:
1、简化跟踪堆中对象的过程(new出来的对象),一个对象通过new被分配出来为了最后进行delete,就要知道谁是这个对象的拥有者。但是对象有多个所有者并且所有权可能会被转移,内存跟踪就会困难。引用计数可以更好的进行跟踪,能够自动的销毁对象,也可以说引用对象就是一个简单的垃圾回收体系。
2、节省内存,提高程序的运行效率。很多对象有多个相同的值,为这些相同的值去存储多个副本是浪费空间的,所以最好的还是让左右对象共享同一个值实现。
三、普通指针存在的问题是什么
在平时我们写代码的时候,用new开辟出来的空间虽然我们知道要进行资源的回收,但可能会因为程序执行流的改变导致资源没有归还所导致的内存泄漏的问题。
{
int* p=new int(5);
...
...
thorw ...;
` ...
delete p;
}
在上面的代码中我们开辟出来的空间最后加上了delete,但是当中间代码抛出一个错误的时候那么就会进行异常处理。如果在异常处理函数中间没有对空间进行释放,那么这块内存就会一直保留在操作系统中,而内存泄漏。
在C、C++中由于没有自动的内存回收的机制,所以内存操作的安全性就完全依赖程序员的自觉性。类似上面的情况导致的内存泄漏的问题还是比较常见的,当我们可以用智能指针的就会致力于解决这种问题,使程序员可以专注于内存的使用而把内存的释放交给智能指针。
四、std::auto_ptr
auto_ptr是在STL中就有的智能指针,auto_ptr是通过权限转移的方式来防止值拷贝锁带来的问题。权限转移是说所开辟的动态内存在任何时刻只能由一个之战指向它。
下面是自己实现的auto_ptr:
template<typename T>
class Auto_ptr//转移权限
{
public:
Auto_ptr(T* ptr = 0)//构造函数
:_ptr(ptr)
{}
Auto_ptr(Auto_ptr<T>& sp)//拷贝构造
{
_ptr = sp._ptr;
sp._ptr = NULL;
}
~Auto_ptr()//析构函数
{
delete _ptr;
}
T* get()
{
return _ptr;
}
Auto_ptr<T>& operator=(const Auto_ptr<T>& sp)//重载赋值运算符
{
delete _ptr;
_ptr = sp._ptr;
sp._ptr = NULL;
return *this;
}
T* operator->()//重载->运算符
{
return _ptr;
}
T& operator*()//重载*运算符
{
return *_ptr;
}
private:
T* _ptr;
};
auto_ptr可以用来管理单个的对象,有一下几点需要注意:
1、尽量不要使用“operator=”,如果使用了就不要再使用先前对象
2、记住release()函数不会释放对象,仅仅归还使用权
3、std::auto_ptr最好不要当成参数传递
4、有std::auto_ptr的“operator=”问题,有其管理的对象不能放入std::vector等容器中
五、boost::scoped_ptr
acoped_ptr和auto_ptr一样都表示唯一的所有权持有者,但是scoped_ptr不允许拷贝构造和赋值的发生,所以将这两个函数定义为私有(保护)成员函数,使外部对象无法对其访问。
下面是对scoped_ptr的简单实现:
template<typename T>
class ScopedPtr
{
public:
ScopedPtr(T* ptr)
:_ptr(ptr)
{}
~ScopedPtr()
{
delete _ptr;
}
T& operator*()
{
retrun *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
ScopedPtr(const ScopedPtr <T>& sp);
ScopedPtr<T>& operator=(const ScopedPtr<T>& sp);
T* _ptr;
};
六、boost::shared_ptr
shared_ptr是采用了引用计数的方式来实现的,shared_ptr允许拷贝和赋值。
当一块空间被创建时引用计数为1,当有新的指针指向这块空间的时候,引用计数就会加1,否则就会减1,直到引用计数减到0的时候才会真正释放这块空间。所以,shared_ptr更像是一个指针。
下面是对shared_ptr的简单实现:
template<typename T>
class SharedPtr
{
private:
T* _ptr;
int* _pcount;//引用计数
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _pcount(new int(1))
{}
SharedPtr(SharedPtr<T>& sp)
{
_ptr = sp._ptr;
_pcount = sp._pcount;
_pcount++;
}
~SharedPtr()
{
if (--*(_pcount) == 0)
{
delete _ptr;
delete _pcount;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
//现代的写法:看看和之前的几个智能指针的赋值运算符有什么不同
SharedPtr<T>& operator=(SharedPtr<T> sp)
{
std::swap(_ptr, sp._ptr);
std::swap(_pcount, sp._pcount);
return *this;
}
};
七、boost::scoped_array
scoped_array是属于boost库用来管理动态数组的,scoped_array和scoped_ptr一样也是独享其所有权的。
下面是对scoped-array的模拟实现:
#include<iostream>
using namespace std;
template<typename T>
class ScopedArray
{
public:
ScopedArray(T* ptr)
:_ptr(ptr)
{}
~ScopedArray()
{
delete _ptr;
}
T& operator[](int index)
{
return _ptr[index];
}
protected:
ScopedArray(const ScopedArray <T>& sp);
ScopedArray<T>& operator=(const ScopedArray<T>& sp);
private:
T* _ptr;
};
int main()
{
ScopedArray <int> p(new int[5]);
p[2] = 10;
cout << p[2] << endl;
system("pause");
return 0;
}
八、boost::shared_array
scoped_array是独享所有权的,在很多情况下比如:参数传递、对象赋值是不满足需求的,所有这时就有了shared_array,shared_array的内部也是使用了引用计数的。
下面是对shared_array的代码实现:
#include<iostream>
using namespace std;
template<typename T>
class SharedArray
{
public:
SharedArray ( T* ptr)
:_ptr(ptr)
,_pcount(new int(1))
{}
SharedArray(SharedArray<T>& sp)
{
_ptr = sp._ptr ;
_pcount = sp._pcount ;
_pcount++;
}
~SharedArray ()
{
if(--*(_pcount) == 0)
{
delete _ptr;
delete _pcount;
}
}
T& operator[](int index)
{
return _ptr[index];
}
SharedArray<T>& operator=(SharedArray<T> sp)
{
std::swap(_ptr,sp._ptr );
std::swap(_pcount,sp._pcount );
return *this;
}
private:
T* _ptr;
int* _pcount;
};
int main()
{
SharedArray<int> sp(new int[10]);
sp[1] = 20;
cout<<sp[1]<<endl;
system("pause");
return 0;
}
九、boost::weak_ptr
weak_ptr是一种不控制所指向对象生存周期的智能指针,它指向一个由shared_ptr管理的对象,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被销毁,即使有weak_ptr指向对象,对象还是会被释放。weak_ptr就是为了解决shared_ptr所产生的循环引用的问题而产生的。
weak_ptr的出现解决了shared_ptr的问题,它采用弱引用的方式,弥补了shared_ptr循环引用的问题它的构造和析构不会引起引用记数的增加或减少。没有重载*和->但可以使用lock获得一个可用的shared_ptr对象
namespace boost
{
template<typename T> class weak_ptr
{
public:
template <typename Y>
weak_ptr(const shared_ptr<Y>& r);
weak_ptr(const weak_ptr& r);
~weak_ptr();
T* get() const;
bool expired() const;
shared_ptr<T> lock() const;
};
}
十、shared_ptr循环引用的问题
我们都知道使用shared_ptr时是用来引用计数的方法来实现的,但是引用计数也给我们带来了新的烦恼,那就是循环引用。
我们先看一个代码:
struct Node
{
int _data;
shared_ptr<Node> _next;
shared_ptr<Node> _prev;
};
void test()
{
shared_ptr<Node> p1(new Node);
shared_ptr<Node> p2(new Node);
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
sp1->_next = p2;
sp2->_prev = p1;
cout << p1.use_count() << endl;
cout <<
p2.use_count() << endl;
}
我们可以看到上面的程序结束的时候p1,p2不会被析构掉。当你析构一块空间的时候,它会去考虑先释放另一块空间,析构另一块空间的时候也是一样。所以,就会导致谁都析构不了,这就是循环引用的问题。
为了解决这个问题,我们就引入了另一个智能指针就是weak_ptr弱指针。weak_ptr是为了辅助shared_ptr,将一个weak_ptr绑定到一个shared_ptr,并不会改变shared_ptr的引用计数,一旦最后一个指向对象的shared_ptr被销毁,对象就会被销毁,即使有weak_ptr指向对象,对象还是会被释放。所以,上边定义的Node结构体改成下边这样,就能解决循环引用的问题。
代码实现:
#include<iostream>
#include<boost\shared_ptr.hpp>
#include<boost\weak_ptr.hpp>
using namespace boost;
using namespace std;
struct Node
{
/*boost::shared_ptr<Node> _next;
boost::shared_ptr<Node> _prve;*/
boost::weak_ptr <Node> _next;//声明为弱指针
boost::weak_ptr <Node> _prve;//声明为弱指针
~Node()
{
cout<<"~Node"<<endl;
}
};
void test()
{
boost::shared_ptr <Node> s1(new Node);
boost::shared_ptr <Node> s2(new Node);
//我们让s1和s2两个结点连起来
s1->_next = s2;
s2->_prve = s1;
cout<<s1.use_count() <<endl;
cout<<s2.use_count() <<endl;
}
int main()
{
test();
system("pause");
return 0;
}
解除这种循环引用有三种方法:
1 . 当只剩下最后一个引用的时候需要手动打破循环引用释放对象。
2 . 当A的生存期超过B的生存期的时候,B改为使用一个普通指针指向A。
3 . 使用弱引用的智能指针打破这种循环引用。
虽然这三种方法都可行,但方法1和方法2都需要程序员手动控制,麻烦且容易出错。我们一般使用第三种方法:弱引用的智能指针weak_ptr。
十一、实现定制删除器的shared_ptr
一般情况下,我们都是用智能指针来管理动态内存的这样就很方便,但当我们用智能指针来管理文件的时候我们就不能使用智能指针自己的删除器了 ,而要为智能指针自己实现一个自己的删除器。
下面我们以文件的打开与删除为例,实现定制删除器。
利用仿函数模拟实现智能指针的定制删除器
#include<iostream>
#include<cstdio>
using namespace std;
//注意对()的重载;
struct Fclose//对文件的关闭
{
void operator()(void* ptr)
{
cout<<"Fclose"<<endl;
fclose((FILE*) ptr);
}
};
struct Default//默认的删除器
{
void operator()(void* ptr)
{
cout<<"~Free()"<<endl;
delete ptr;
}
};
//注意:两个模板参数,第二个模板参数有默认值;
template<typename T,typename D = Default>
class SharedPtr
{
public:
SharedPtr ( T* ptr)
:_ptr(ptr)
,_pcount(new int(1))
,_del(D())
{}
SharedPtr(SharedPtr<T,D>& sp)
{
_ptr = sp._ptr ;
_pcount = sp._pcount ;
_pcount++;
}
~SharedPtr ()
{
if(--(*_pcount) == 0)
{
_del(_ptr);//注意对()重载的使用;
delete _pcount;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
SharedPtr<T,D>& operator=(SharedPtr<T,D> sp)
{
std::swap(_ptr,sp._ptr );
std::swap(_pcount,sp._pcount );
return *this;
}
private:
T* _ptr;
int* _pcount;
D _del;//删除器类型成员;
};
int main()
{
SharedPtr<FILE,Fclose> s( fopen("test2.txt","w"));
SharedPtr<int> p(new int);//测试默认删除类型;
//system("pause");
return 0;
}
十二、总结
这是C++11中的智能指针与boost库中的智能指针的比较,而boost库是因为auto_ptr而有了后面的智能指针。
1、shared_ptr是通过引用计数的方式来管理所指的对象
2、shared_ptr可以用一个new的表达式返回指针进行初始化,但是不能将new表达式的返回值赋值给shared_ptr
3、一旦将一个new表达式返回的指针交由shared_ptr管理之后,就不要再通过普通指针访问这块内存
4、shared_ptr可以通过reset方法重置指向另一个对象,此时原对象的引用计数减一;可以定制一个deleter函数,用于在shared_ptr释放对象时调用
5、在确定对象无需共享的情况下,使用scoped_ptr(当然动态数组使用 scoped_array)。
6、在对象需要共享的情况下,使用shared_ptr(当然动态数组使用 shared_array)。
7、不可以对scoped_ptr进行拷贝、赋值等操作,但是可以通过release函数在scoped_ptr之间转移控制权。
8、weak_ptr一般和shared_ptr配合使用。它可以指向shared_ptr所指向的对象,但是却不增加对象的引用计数
9、在代码中,不要出现 delete 关键字(或 C 语言的 free 函数),因为可以用智能指针去管理。
10、其中用到了boost库,这个boost库需要在网上下载好自己加入路径,把boost库复制好,然后找到vs的安装路径,打开找到vc,再打开vc找到include文件夹,将boost库复制到include文件夹中即可使用。