C++之智能指针

Posted 老猫想编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++之智能指针相关的知识,希望对你有一定的参考价值。

前言

之前在内存泄露与内存溢出这篇文章中提到,造成内存泄露的一大原因就是malloc/free或者new/delete未成对出现。而人往往会疏忽大意,这种错误往往是无法全部避免的。那么我们就需要寻求一种方法,可以自动申请和释放指针。这种方法就是智能指针。

简介

智能指针是一种资源管理类,这个类在构造函数中传入了一个原始指针,然后在析构函数中释放传入的指针。智能指针都分配在栈上,当函数结束的时候就会自动释放。为了使智能指针对象具有指针的特征,还需要对*->操作符进行重载。

C++中的智能指针有auto_ptrunique_ptrshared_ptrweak_ptr四种。

auto_ptr

auto_ptr就是智能指针思想的一个最原始的实现。其SGI-STL版中的实现源码如下:

template<class X>class auto_ptr {private:
X
* ptr;
mutable bool owns;public:
typedef X element_type;
explicit auto_ptr(X* p = ) __STL_NOTHROW : ptr(p), owns(p) {}
auto_ptr
(const auto_ptr& a) __STL_NOTHROW : ptr(a.ptr), owns(a.owns) {
a
.owns = ;
}
template<class T> auto_ptr(const auto_ptr<T>& a) __STL_NOTHROW
: ptr(a.ptr), owns(a.owns) {
a
.owns = ;
}

auto_ptr
& operator=(const auto_ptr& a) __STL_NOTHROW {
if (&a != this) {
if (owns)
delete ptr;
owns
= a.owns;
ptr
= a.ptr;
a
.owns = ;
}
}
template<class T> auto_ptr& operator=(const auto_ptr<T>& a) __STL_NOTHROW {
if (&a &= this) {
if (owns)
delete ptr;
owns
= a.owns;
ptr
= a.ptr;
a
.owns = ;
}
}
~auto_ptr() {
if (owns)
delete ptr;
}

X
& operator*() const __STL_NOTHROW { return *ptr; }
X
* operator->() const __STL_NOTHROW { return ptr; }
X
* get() const __STL_NOTHROW { return ptr; }
X
* release() const __STL_NOTHROW { owns = false; return ptr; }};

可以从中看出,auto_ptr有一个指针和所有权,然后定义了构造函数和析构函数,重载下了操作符=来转让所有权,重载了操作符*->来实现指针特性,以及其他的函数。

unique_ptr

我们来看一个例子:

auto_ptr<string>ps(new string("I am an man"));
auto_ptr
<string>pa;
pa
=ps;

上述操作会导致BUG!

如果ps和pa是常规指针,则两个指针指向同一个string对象,当ps和pa分别过期时,系统会将这个对象删除两次!!

那么怎么去避免这种情况呢?方法也有很多:

  • 定义赋值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中一个对象是另一个对象的副本。

  • 建立所有权概念,对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针可以删除该对象。然后,让复制操作转让所有权。这就是auto_ptr和unique_ptr的策略,但unique_ptr的策略更加严格。

  • 创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。如,赋值时,计数加1;指针释放时,计数减1。仅当最后一个指针过期时,才彻底删除该对象。这是shared_ptr所采用的策略。

上述例子中,auto_ptr可以防止ps和pa的析构函数试图删除同一个对象,但是如果程序再次调用ps呢?ps不再指向有效的数据,又是BUG!

但是,如果用unique_ptr呢?

unique_ptr<string>ps(new string("I am an man"));
unique_ptr
<string>pa;
pa
=ps;

编译器会认为这种操作非法,因此,unique_ptr比auto_ptr要更安全!

而且,实际中的编译器更智能。当程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是一个临时右值,编译器允许这样做;如果源unique_ptr将存在一段时间,编译器将禁止这样做。

所以,auto_ptr已经被废除。

shared_ptr

unique_ptr是为了解决auto_ptr在对象所有权上的局限性,在使用引用计数的机制上提供了可以共享所有权的智能指针。

多个unique_ptr指针可以指向同一对象,该对象和其相关资源最后一个引用被销毁的时候释放。它使用计数机制来标明资源被几个指针共享,可以通过成员函数use_count()来查看资源的所有者计数。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。

shared_ptr可能导致的问题

  • shared_ptr智能指针对象中的引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时进行++和–操作,在没有加锁的情况下,会导致计数混乱;

  • 智能指针管理的对象保存在堆上,两个线程同时去访问,也会导致线程安全问题;

  • 引入引用计数后出现的循环引用问题。

weak_ptr

我们来看一个shared_ptr指针循环引用的例子:

class B;struct A{
shared_ptr
<B> b;};struct B{
shared_ptr
<A> a;};auto pa = make_shared<A>();auto pb = make_shared<B>();
pa
->b = pb;
pb
->a = pa;

pa 和 pb 存在着循环引用,两个资源的引用计数都为2,当pa 和 pb 析构时,两个资源引用计数减一,但是两者的引用计数仍然为1,所以资源仍然没有释放。

那么如何解决这个问题呢?答案就是引入weak_ptr指针。

weak_ptr指针是一种不控制对象生命周期的智能指针,它指向一个shared_ptr管理的对象。weak_ptr指针只是提供了一个对管理对象的访问手段。weak_ptr指针设计的目的就是为了配合和协助shared_ptr指针工作。

weak_ptr指针智能从一个shared_ptr指针或者另一个weak_ptr指针构造,它的构造和析构都不会引起引用计数的增加和减少。因而,它可以解决shared_ptr相互循环引用导致的思索问题。

weak_ptr指针和shared_ptr指针之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

动手实现一个智能指针

代码如下:

template <typename T>class SmartPointer {private:
T
* _ptr;
size_t* _reference_count;public:
//构造函数
SmartPointer(T* p=nullptr): _ptr(p), _reference_count(new size_t){
if(p)
*_reference_count = 1;
else
*_reference_count = 0;
}
//拷贝构造函数
SmartPointer(const SmartPointer& src) {
if(this!=&src) {
_ptr
= src._ptr;
_reference_count
= src._reference_count;
(*_reference_count)++;
}
}
//重载赋值操作符
SmartPointer& operator=(const SmartPointer& src) {
if(_ptr==src._ptr) {
return *this;
}
releaseCount
();
_ptr
= src._ptr;
_reference_count
= src._reference_count;
(*_reference_count)++;
return *this;
}

//重载操作符
T
& operator*() {
if(ptr) {
return *_ptr;
}
//throw exception
}
//重载操作符
T
* operator->() {
if(ptr) {
return _ptr;
}
//throw exception
}
//析构函数
~SmartPointer() {
if (--(*_reference_count) == 0) {
delete _ptr;
delete _reference_count;
}
}private:
void releaseCount() {
if(_ptr) {
if((--*_reference_count)==0) {
delete _ptr;
delete _reference_count;
}
}
}};


以上是关于C++之智能指针的主要内容,如果未能解决你的问题,请参考以下文章

C++智能指针(3.30)

❥关于C++之智能指针

C++智能指针之shared_ptr与右值引用(详细)

C++智能指针之shared_ptr与右值引用(详细)

C++智能指针之shared_ptr与右值引用(详细)

更新:C++ 指针片段