智能指针
智能指针
概念
简单来说智能指针就是一个类似于指针的玩意,帮我们管理一个对象,不需要我们去手动释放指针所指对象。
智能指针(英语:Smart pointer)是一种抽象的数据类型。在程序设计中,它通常是经由类模板来实现,借由模板来达成泛型,借由类别的析构函数来达成自动释放指针所指向的存储器或对象。
智能指针一般会遵循以下两点:
- 遵循RAII思想——资源分配即初始化
- 重载
*
、->
运算符,有些针对数组的智能指针也会重载[]
RAII思想
RAII是英文Resource Acquisition Is Initialization的缩写,指的是资源分配即初始化。
RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。
重重载*
、->
运算符
因为对于指针而言,指针访问一个对象内部成员通常是使用解引用*
加.
或者->
来访问,比如指针p指向的一个对象中有一个成员num,那么我们访问使用的是(*p).num
或者p->num
,但是智能指针本身是个类,所以重载*
、->
是为了让和指针一样去访问成员,方便使用。
重载->
运算符
我们重载运算符的时候,在使用过程中运算符会被消耗掉,比如重载*
,*p
就相当于operator*()
,在智能指针中返回的是该智能指针所指对象。
类比,如果我们用->
,实际是相当于operator->()
,此时这个运算符就被消耗了,返回一个智能指针所指对象的指针,问题是如果我们要接着访问对象内成员的话,应该还需要一个->
,也就是说我们在用的时候应这么用p->->member
,但这样极其不便于书写和阅读习惯,因此在这里编译器对其处理成只需要一个->
就能访问成员,这里是比较特殊的地方。
为什么需要智能指针
在C++中由于有异常这种扰乱程序执行流程的东西,尽管我们写了new之后马上跟了delete,但是函数里面new了之后还没执行到delete就抛异常跳出函数,导致对象没有被释放造成内存泄漏问题,防不胜防,或者我压根就忘了释放,这个时候就是智能指针登场的时候了。
智能指针本身是一个类,由于类的对象创建时系统会自动调用构造函数,而当对象生命周期结束时,系统会自动调用析构函数,让对象能够自动释放,这样就无需我们手动去管理,也不会出现因执行流被打断造成的内存泄漏问题。
智能指针的历史
- auto_ptr:C++98标准中提供了该类的智能指针,但是这个智能指针是个残疾,当使用另一个auto_ptr拷贝时,原指针会被置空,严重影响体验,公司不让用。
- scoped_ptr:这个智能指针由Boost的大佬们实现,原因是auto_ptr就是个残废,于是有了这个指针,scoped_ptr是auto_ptr的防拷贝版本,解决了auto的不足,日常使用满足需求,公司推荐使用。
- shared_ptr:该指针也是Boost库中的指针,这个版本引入了引用计数实现共享指针,但是会有智能指针相互循环引用的问题,在特定场景会内存泄漏,公司推荐使用。
- weak_ptr:Boost库的智能指针,用于解决shared_ptr循环引用的问题,可以接收一个shared_ptr的对象,并指向shared_ptr指向的对象,引用计数独立。
- unique_ptr/shared_ptr/weak_ptr:C++11标准提供,由于Boost库已经实现的很成熟,因此C++11实现的和其很类似,使用也基本没有差别,其中unique_ptr就是Boost库中的scoped_ptr。
shared_ptr的循环引用
当有如下类
struct ListNode
{
shared_ptr<ListNode> _next;
shared_ptr<ListNode> _prev;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
如果存在两个指针p1和p2
- p1->_next = p2;
- p2->_prev = p1;
此时存在循环引用问题,析构p1的前提是p2->_prev要析构(因为引用计数为2,光析构一个不行),析构p2的前提是p1->_next要析构,这就很麻烦,到了最后析构的时候,引用计数只减了1,两个对象还是没析构掉,反而出现了内存泄漏。。
如何解决
Boost库提供了一种智能指针叫weak_ptr
专门解决这类问题,只需要将上述的结构体定义如下,便能解决,由于weak_ptr
不影响shared_ptr
的引用计数,所以释放时会正常释放
struct ListNode
{
weak_ptr<ListNode> _next;
weak_ptr<ListNode> _prev;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
库中的智能指针使用
单个对象
单个对象比较简单,不多赘述,看代码即可
struct TestClass
{
public:
TestClass()
{
cout << "TestClass()" << endl;
}
~TestClass()
{
cout << "~TestClass()" << endl;
}
private:
int num;
};
void test()
{
unique_ptr<TestClass> up(new TestClass);
shared_ptr<TestClass> sp1(new TestClass);
shared_ptr<TestClass> sp2(sp1);
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
cout << sp1.get() << endl;
cout << sp2.get() << endl;
}
多个对象(数组)或特殊处理类型(FILE指针、malloc/free)
多个对象如果默认不处理,最后只会析构一个对象,甚至可能出错,特殊类型也不能用delete简单处理,比如FILE指针必须用fclose处理,这里就涉及到仿函数处理特殊类型了,自定义一个类,重载()
,在创建智能指针时把其对象作为第二个参数传入,处理即按照仿函数处理析构,第二个参数就是给我们自定析构用的,默认用delete,自定可以自定析构方式,如delete[]、free、fclose等,实现自动管理。
template <class T>
class DeleteArray
{
void operator()(T* ptr)
{
cout << "DeleteArray delete array" << endl;
delete[] ptr;
}
};
template <class T>
class DeleteFILE
{
void operator()(T* ptr)
{
cout << "DeleteArray delete array" << endl;
fclose(ptr);
}
};
void testArray()
{
unique_ptr<TestClass[]> up_group(new TestClass[10]);
//shared智能指针管理数组,自定义删除模式
shared_ptr<TestClass> sp_group(new TestClass[10], DeleteArray<TestClass>());
//管理特殊类型
shared_ptr<FILE> file(fopen("text.txt", "w"), DeleteFILE<FILE>());
}
仿函数
仿函数就是重载了()
,比如类A重载了一个函数,有两个int参数,如void operator()(int x1, int x2);
,这个时候我们就可以这么调用这个成员,A(1, 2);
,由于其调用方式很像函数的调用,但本质是重载了运算符()
,因此称之为仿函数。
线程安全问题
使用shared_ptr时会有一个安全问题,由于shared_ptr是可以拷贝的,指向内容可以被多个智能指针访问,如果这些指针在不同的线程内,那么会出现线程安全问题,引用计数也是如此,可能存在短时间内被两个指针访问修改的情况,那么就会出问题。
shared_ptr的线程安全可以分为两层:
- 引用计数线程安全由指针维护,无需我们多管
- 指针所指向内容需要用户自己去维护,这样就需要我们对临界资源进行控制,linux下使用信号量或者互斥锁,Windows的话。。不清楚
智能指针的简单实现
auto_ptr
template <class T>
class AutoPtr
{
public:
AutoPtr(T* ptr = new T)
:_ptr(ptr)
{}
AutoPtr(AutoPtr<T>& sp)
:_ptr(sp._ptr)
{
sp._ptr = NULL;
}
~AutoPtr()
{
delete _ptr;
}
AutoPtr<T>& operator=(AutoPtr<T>& sp)
{
//在auto_ptr中,如果赋值就把原指针直接给置空了,这是一件很蠢的事情
_ptr = sp._ptr;
sp._ptr = NULL;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
//这个版本没有测试用例
scoped_ptr
防拷贝的实现使用delete关键字,删除拷贝构造和operator=()
,表明函数已经被删除,无法调用。
template <class T>
class ScopedPtr
{
public:
ScopedPtr(T* ptr = new T)
:_ptr(ptr)
{}
~ScopedPtr()
{
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
ScopedPtr(ScopedPtr<T>& sp) = delete;
ScopedPtr<T>& operator=(ScopedPtr<T>& sp) = delete;
ScopedPtr<T>& operator=(T* ptr) = delete;
protected:
T* _ptr;
};
void testScopePtr()
{
ScopedPtr<string> sp;
//ScopedPtr<string> sp1(sp);//错误,拷贝构造为私有无法调用
//sp1 = sp;//错误,=运算符重载后为私有,无法调用
*sp = "hello world";
cout << *sp << endl;
sp->at(0) = 'H';//调用string类的at
cout << *sp << endl;
}
shared_ptr
template <class T>
class SharedPtr
{
template <class>
friend class WeakPtr;
public:
SharedPtr(T* ptr = NULL)
:_ptr(ptr)
,_count(new size_t(1))
{}
SharedPtr(SharedPtr<T>& sp)
:_ptr(sp._ptr)
, _count(sp._count)
{
(*_count)++;
}
~SharedPtr()
{
if (--(*_count) == 0)
{
delete _ptr;
delete _count;
}
}
SharedPtr<T>& operator=(SharedPtr<T>& sp)
{
if (_ptr != sp._ptr)
{
if (--(*_count) == 0)
{
delete _ptr;
delete _count;
}
_ptr = sp._ptr;
++(*sp._count);
_count = sp._count;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
size_t *_count;
};
void testSharedPtr()
{
SharedPtr<string> sp = new string;
SharedPtr<string> sp1;//可以使用
sp1 = sp;//可以使用
*sp = "hello world";
cout << *sp << endl;
sp->at(0) = 'H';//调用string类的at
cout << *sp1 << endl;
}
struct List
{
SharedPtr<List> _next;
SharedPtr<List> _prev;
List()
:_next(NULL)
, _prev(NULL)
{}
~List()
{
cout << "~List()" << endl;
}
};
void testSharedPtrCircularReference()
{
//这里压根就调不到析构函数,因为引用计数互相引用后为2
//出作用域后计数为1,还没达到析构条件
//循环引用问题
SharedPtr<List> p1 = new List();
SharedPtr<List> p2 = new List();
p1->_next = p2;
p2->_prev = p1;
}
weak_ptr
弱指针用于处理循环引用的问题
template <class T>
class WeakPtr
{
public:
WeakPtr()
:_ptr(NULL)
{}
WeakPtr(const SharedPtr<T>& sp)
:_ptr(sp._ptr)
{}
WeakPtr(WeakPtr<T>& wp)
:_ptr(wp._ptr)
{}
WeakPtr<T>& operator=(SharedPtr<T>& sp)
{
_ptr = sp._ptr;
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
protected:
T* _ptr;
};
struct ListNode
{
WeakPtr<ListNode> _next;
WeakPtr<ListNode> _prev;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
// 循环引用
void TestCycle()
{
SharedPtr<ListNode> n1 = new ListNode;
SharedPtr<ListNode> n2 = new ListNode;
n1->_next = n2;
n2->_prev = n1;
}