【C++11】智能指针
本文介绍了C++智能指针的原理与实现,重点讨论了RAII资源管理思想及其在智能指针中的应用。主要内容包括: RAII技术原理:通过对象生命周期管理资源,构造函数获取资源,析构函数释放资源,避免内存泄漏。 C++智能指针发展历程:从C++98的auto_ptr到C++11引入的unique_ptr、shared_ptr和weak_ptr。 三种主要智能指针的实现与特点。文章通过示例代码详细说明了各类
文章目录
前言
我们都知道使用了new申请了堆区空间之后都需要手动释放资源。这样的情况在编程过程中十分的常见,甚至有些时候由于一些异常等原因,我们的程序无法完成我们的释放工作,从而导致内存泄露。此时有人提出了RAII的思想,我们C++11正式推出智能指针的语法。本质就是利用RAII。
本文章小编会带大家了解RAII和C++智能指针的用法和模拟实现
1. RAII
-
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。
这种做法有两大好处:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效。
例1:
//正确包含头文件
class A
{
public:
A()
{
if (nullptr == _ptr)
{
_ptr = new int;
cout << "申请成功" << endl;
}
}
~A()
{
if (nullptr != _ptr)
{
delete _ptr;
cout << "释放成功" << endl;
}
}
private:
int* _ptr;
};
int main()
{
cout << "程序开始" << endl;
{
//局部域
A a;
}
cout << "程序结束" << endl;
return 0;
}
上面代码中,我们的
A类中包含一个new的整型指针,在创建A类对象的时候对指针进行初始化,而当局部域结束过后,该对象自动调用析构函数,从而释放资源。、
所以,我们可以利用上面例1的这样的形式,可以将一个指针交给一个类来管理。这便是智能指针的雏形。
2. C++智能指针和Boost库
- C++ 98 中产生了第一个智能指针
auto_ptr. - C++
boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr. - C++ TR1,引入了
shared_ptr等。不过注意的是TR1并不是标准版。 - C++ 11,引入了
unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。
3. 智能指针
-
头文件:
memory -
需求:
- 被类包装了
- 具有指针的操作:
*, ->, =……(常见的)
所以很明显:运算符重载就发挥了极大的作用!!!
- 注意:这里需要特别强调的是关于指针的赋值操作!!
3.1 auto_ptr
-
文档:auto_ptr
-
auto_ptr:C++98版本的库中就提供了auto_ptr的智能指针。 -
特点:
- auto_ptr赋值操作,会将指针的管理权转移。即,一旦
auto_ptr发生了拷贝赋值操作,那么原来的指针变量不再起作用了……造成了被拷贝对象悬空的问题。
- auto_ptr赋值操作,会将指针的管理权转移。即,一旦
例2:
#include <memory>
int main()
{
auto_ptr<int> p1(new int(1));
auto_ptr<int> p2;
p2 = p1;
//++(*p1); //非常危险的代码
++(*p2);
return 0;
}
- 从监视窗口观察:

所以上面代码中的:
++(*p1)是十分危险的。
所以auto_ptr的局限性非常强,不适合日常使用!
3.1.1 auto_ptr实现
namespace Er
{
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
: _ptr(ptr)
{}
auto_ptr()
:_ptr(nullptr)
{}
auto_ptr(auto_ptr<T>& ap)
: _ptr(ap._ptr)
{
ap._ptr = nullptr; //赋值的置为空
}
const auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if(this != ap)
{
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
~auto_ptr()
{
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
}
3.2 unique_ptr
-
文档:unique_ptr
-
特点:
- 直接防止赋值和拷贝。仅允许一个指针存在。
例3:
3.2.1 unique_ptr实现
namespace Er
{
template<class T>
class unique_ptr
{
public:
unique_ptr(T* ptr)
: _ptr(ptr)
{}
//方法二:接后缀delete --> 代表删除
unique_ptr(unique_ptr<T>& up) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
~unique_ptr()
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
//方法一: 只声明不实现 -- 放在私有
//unique_ptr(unique_ptr<T>& up);
//unique_ptr<T>& operator=(const unique_ptr<T>& up);
T* _ptr;
};
}
3.3 shared_ptr
-
文档:shared_ptr
-
特点:
几乎和指针的行为一致。支持赋值和拷贝,且能正常释放资源。
-
问题:循环引用。
例5:
#include <memory>
int main()
{
shared_ptr<int> p1(new int);
shared_ptr<int> p2(new int);
p2 = p1;
shared_ptr<int> p3(p1);
return 0;
}
3.3.1 shared_ptr的简单实现
为什么说简单?因为我们需要解决多线程的竞争问题。
-
问题一:我们都知道,指针指向的资源只有一份。多个指针指向同一份资源,如何做到成功释放资源的呢?
举个例子,现在我们一个班有50个学生,但是灯光资源只有一份。所以当我们所有人都离开教室的时候,这个灯光我们才关闭。
简要地说:最后一个离开的释放资源。
这就是计数引用的思想。
-
问题二:我们如何让多个指针对象看到同一份计数资源呢?
采用堆区的资源。
但是我们不能采用静态成员变量的形式。静态成员变量是属于类的,但是我们要求这个计数引用对于指向资源的数量统计!!!
namespace Er
{
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
: _ptr(ptr)
, _pcount(new int(1))
{}
shared_ptr(const shared_ptr<T>& sp)
: _ptr(sp._ptr)
, _pcount(sp._pcount)
{
++(*_pcount);
}
//赋值运算符需要考虑的场景:
// 1、sp3 = sp1 指向同一个位置
// 2、sp4 = sp4 刚好创建,自己给自己赋值
// 3、sp2 = sp1 另一个指向的指针,指向同一空间
// --> 刚好是最后一个/不是
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (_ptr == sp._ptr) //防止自我赋值
return *this; //指向同一片空间就停止赋值
if (--(*_pcount) == 0) //如果自己是最后一个
{
delete _ptr;
delete _pcount;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount);
return *this;
}
~shared_ptr()
{
if (--(*_pcount) == 0) //保证最后一个对象析构
{
delete _ptr;
delete _pcount;
}
}
T* get() const
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
int* _pcount;
//不可行方案:
//int _count;
//static int _count;
};
}
3.3.2 循环计数和weak_ptr
来看下面的场景:
例6:
struct Node
{
int a;
shared_ptr<Node> _prev;
shared_ptr<Node> _next;
}
int main()
{
shared_ptr<Node> n1(new Node);
shared_ptr<Node> n2(new Node);
n1->_next = n2;
n2->_prev = n1;
}
-
上面代码有什么问题?
两个节点的资源都无法释放!!!下面给出图解

这就是循环引用的问题! -
weak_ptr就是专门用于解决shared_ptr的循环引用的问题的,因为它不增加计数引用!所以它没有自己的专门的构造。
文档:weak_ptr -
不过注意:
weak_ptr不是RAII。
例7:
struct Node
{
int a;
weak_ptr<Node> _prev;
weak_ptr<Node> _next;
}
int main()
{
shared_ptr<Node> n1(new Node);
shared_ptr<Node> n2(new Node);
n1->_next = n2;
n2->_prev = n1;
}
- 模拟实现:
namespace Er
{
template<class T>
class weak_ptr
{
public:
weak_ptr(T* ptr = nullptr)
: _ptr(ptr)
{}
weak_ptr(const weak_ptr<T>& wp)
: _ptr(wp._ptr)
{}
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
~weak_ptr()
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
}
3.3.3 shared_ptr的定制删除器
我们来看shared_ptr的文档:

这是为什么呢?
来看这样的场景:
//面对这三种情况如何应对呢?
shared_ptr<A> sp1(new A[4]);
shared_ptr<A> sp2((A*)malloc(sizeof(A)));
shared_ptr<FILE> sp3(fopen("data.txt","r"));
所以我们必须把自己的释放资源的方法传入。
如果我们需要完善我们的自行的shared_ptr,我们必须想办法能够有参数接受各种删除方法。
可喜的是:这些删除办法都是可执行对象。并且返回值和参数类型都是:void(void)我们可以使用包装器来完成,定制删除器。
例:
namespace Er
{
template<class T>
class shared_ptr
{
public:
template<class D>
shared_ptr(T* ptr, D del)
: _ptr(ptr)
, _pcount(new int(1))
, _del(del)
{}
shared_ptr(T* ptr = nullptr)
: _ptr(ptr)
, _pcount(new int(1))
{}
shared_ptr(const shared_ptr<T>& sp)
: _ptr(sp._ptr)
, _pcount(sp._pcount)
, _del(sp._del)
{
++(*_pcount);
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (_ptr == sp._ptr)
return *this; //指向同一片空间就停止赋值
if (--(*_pcount) == 0)
{
cout << "ptr析构成功" << endl;
_del(_ptr);
delete _pcount;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
_del = sp._del;
++(*_pcount);
return *this;
}
~shared_ptr()
{
if (--(*_pcount) == 0)
{
cout << "ptr析构成功" << endl;
_del(_ptr); //调用删除器
delete _pcount;
}
}
T* get() const
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
int* _pcount;
function<void(T*)> _del = [](T* ptr) {delete ptr; };
};
}
完。
希望这篇文章能够帮助你!!!
更多推荐

所有评论(0)