前面一篇文章,我讲解了智能指针的原理,并实现了一个简单的智能指针。为了加深对智能指针的理解,在这篇文章中,我把C++中的几个智能指针讲解下:auto_ptr, unique_ptr, shared_ptr, weak_ptr。
1、auto_ptr
前面的文章我们把smart_ptr的拷贝构造函数、赋值运算符都禁掉了。auto_ptr在拷贝构造函数和赋值运算符里是怎么处理的呢?它会把原来的指针赋为nullptr。
template<class T>
class auto_ptr {
public:
auto_ptr(T* _ptr) : ptr(_ptr) {
}
auto_ptr(auto_ptr& _ap) : ptr(_ap.ptr) {
_ap.ptr = nullptr;
}
auto_ptr<T>& operator = (auto_ptr<T>& _ap) {
if (ptr != _ap.ptr) {
ptr = _ap.ptr;
_ap.ptr = nullptr;
}
return *this;
}
~auto_ptr() {
delete ptr;
ptr = nullptr;
}
T& operator * () {
return *ptr;
}
T* operator -> () {
return ptr;
}
private:
T* ptr;
};
3、unique_ptr
unique_ptr是c++11版本库中提供的智能指针,它直接将拷贝构造函数和赋值重载函数给禁用掉。所以unique_ptr只能move,不能赋值。
4、shared_ptr
shared_ptr允许多个智能指针可以指向同一块资源,并且能够保证共享的资源只会被释放一次。
shared_ptr采用的是引用计数原理来实现多个shared_ptr对象之间共享资源:
(1)引用计数用来记录该资源被几个对象共享。
(2)当一个shared_ptr对象被销毁时(调用析构函数),析构函数内就会将该计数减1。
(3)如果引用计数减为0后,则表示自己是最后一个使用该资源的shared_ptr对象,必须释放资源。
(4)如果引用计数不是0,就说明自己还有其他对象在使用,则不能释放该资源。
在使用shared_ptr时,要注意不要让2个shared_ptr指向同一个原始指针,比如:
A* p = new A(10);
shared_ptr<A> sp1(p), sp2(p);
sp1 和 sp2 并不会共享同一个对 p 的引用计数,而是各自将对 p 的引用计数都记为 1(sp2 无法知道 p 已经被 sp1 托管过)。这样,当 sp1 消亡时要析构 p,sp2 消亡时要再次析构 p,这会导致程序崩溃。
5、weak_ptr
weak_ptr类的对象可以指向shared_ptr,但不会改变shared_ptr的引用计数。一旦最后一个shared_ptr被销毁时,对象就会被释放。
weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象,典型的用法是调用其lock函数来获得shared_ptr示例,进而访问原始对象。
我们来看一个shared_ptr交叉引用的例子:
class B;
class A
{
public:
shared_ptr<B> sp;
~A(){
cout << "~A"<<endl;
}
};
class B
{
public:
shared_ptr<A> sp;
~B(){
cout << "~B"<<endl;
}
};
void fun() {
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
pb->sp = pa;
cout << "pb.use_count " << pb.use_count() << endl;//1
cout << "pa.use_count " << pa.use_count() << endl;//2
pa->sp = pb;
cout << "pb.use_count " << pb.use_count() << endl;//2
cout << "pa.use_count " << pa.use_count() << endl;//2
//并没有输出 ~A, ~B,也就是class B;
class A
{
public:
shared_ptr<B> sp;
~A(){
cout << "~A"<<endl;
}
};
class B
{
public:
shared_ptr<A> sp;
~B(){
cout << "~B"<<endl;
}
};
void fun() {
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
pb->sp = pa;
cout << "pb.use_count " << pb.use_count() << endl;//1
cout << "pa.use_count " << pa.use_count() << endl;//2
pa->sp = pb;
cout << "pb.use_count " << pb.use_count() << endl;//2
cout << "pa.use_count " << pa.use_count() << endl;//2
//没有输出~A, ~B。也就是没有调用A和B的析构函数。
}
怎么去避免这种交叉引用呢?这就需要使用weak_ptr:把A中的shared_ptr<B> sp改为weak_ptr<B> sp_weak,这样传递时不会增加sp引用计数use_count()的值,所以最终能够使A、B资源正常释放:
class B;
class A
{
public:
//shared_ptr<B> sp;
weak_ptr<B> sp_weak;
~A(){
cout << "~A"<<endl;
}
};
class B
{
public:
shared_ptr<A> sp;
~B(){
cout << "~B"<<endl;
}
};
void fun() {
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
pb->sp = pa;
cout << "pb.use_count " << pb.use_count() << endl;//1
cout << "pa.use_count " << pa.use_count() << endl;//2
//pa->sp = pb;
pa->sp_weak = pb;
cout << "pb.use_count " << pb.use_count() << endl;//1
cout << "pa.use_count " << pa.use_count() << endl;//2
shared_ptr<B> pa2 = pa->sp_weak.lock();
}