C++智能指针剖析(上)std::auto_ptr与boost::scoped_ptr
1. 引入
C++语言中的动态内存分配没有自动回收机制,动态开辟的空间需要用户自己来维护,在出函数作用域或者程序正常退出前必须释放掉。 即程序员每次 new 出来的内存都要手动 delete,否则会造成内存泄露, 有时我们已经非常谨慎了 , 然防不胜防:流程太复杂,程序员忘记 delete;异常导致程序过早退出,没有执行delete的情况屡见不鲜。
void FunTest()
{
int *p = new int[];
FILE* pFile = fopen("1. txt", "w");
if (pFile == NULL)
{
return; //如果pFile == NULL则p指向的空间得不到释放
}
// DoSomethint() ;
if (p != NULL)
{
delete[] p;
p = NULL;
}
}
void FunTest2() //异常导致程序提前退出
{
int *p = new int[];
try
{
DoSomething();
}
catch(. . .)
{
return;
}
delete[] p;
}
在前面的异常处理一节中已经提到过可以定义一个类来管理资源的分配与初始化,把释放资源的部分交给析构函数来处理,即RAII(Resource Acquisition Is Initialization)直译过来即“资源获取即初始化”,是C++语言的一种管理资源、避免泄漏的惯用法。其实从字面译过来的意思并不全面,因为它只提到了一个方面,还有另一个同样重要的是空间释放与资源回收。RAII:定义一个类来封装资源的分配和释放, 在构造函数中完成资源的分配和初始化, 在析构函数中完成资源的清理, 可以保证资源的正确初始化和释放。C++的这种资源管理机制保证了内存空间的正确使用,在任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。
RAII本身不是智能指针,它是一种规范,一种解决问题的思想。智能指针是RAII的一种应用,智能管理资源,可以像指针一样使用,但它不是指针。
2. 智能指针总述
用智能指针便可以有效缓解上面提到的问题,本文主要讲解常见的智能指针的用法。Boost库的智能指针如下:(ps: 新的C++11标准中已经引入了unique_ptr/shared_ptr/weak_ptr)
对于编译器来说,智能指针实际上是一个栈对象,并非指针类型,在栈对象生命周期即将结束时,智能指针通过析构函数释放由它管理的堆内存。所有智能指针都重载了“operator->”和“operator*”操作符,直接返回对象的引用,用以操作对象。访问智能指针原来的方法则使用“.”操作符。(智能指针包含了 reset() 方法,如果不传递参数(或者传递 NULL),则智能指针会释放当前管理的内存。如果传递一个对象,则智能指针会释放当前对象,来管理新传入的对象。)
2.1 被抛弃的std::auto_ptr
std::auto_ptr 属于 STL,当然在 namespace std 中,包含头文件 #include<memory> 便可以使用。std::auto_ptr 能够方便的管理单个堆内存对象。auto_ptr用于指向一个动态分配的对象指针,他的析构函数用于删除所指对象的空间,以此达到对对象生存期的控制。 auto_ptr本质是管理权限的转移。在进行赋值,拷贝构造时,会对控制权进行转移。怎么理解呢?我们用图来解释其中的问题。
上图中的拷贝构造与赋值操作就可以体现auto_ptr 管理资源的本质,为了更清楚的了解auto_ptr ,下面我将模拟实现auto_ptr 的管理机制:
template <class T>
class AutoPtr
{
public:
AutoPtr(T* ptr = NULL)
:_Ptr(ptr)
{}
AutoPtr(AutoPtr<T>& ap)
:_ptr(ap._ptr){ //始终只有一个对象管理一块空间
ap._Ptr = NULL;
}
AutoPtr<T>& operator=(const AutoPtr<T>& ap) {
if (this != &ap) { //始终只有一个对象有管理这块空间的权限
delete this->_Ptr;
this->_ptr = ap._Ptr;
ap._Ptr = NULL;
}
return *this;
}
~AutoPtr() {
delete _ptr;
}
T& operator*() {
if (_ptr = NULL) {
throw ap; //对空指针解引用时抛出异常
}
return *_ptr;
}
T* operator->() {
if (_ptr = NULL) {
throw ap; //使用箭头访问空指针时抛出异常
}
return _ptr;
}
bool operator==(const AutoPtr<T>& ap) {
return _ptr == ap._Ptr;
}
bool operator!=(const AutoPtr<T>& ap) {
return _ptr != ap._Ptr;
}
void Reset(T* ptr = NULL) { //删除原有指针_ptr并获得指针ptr的所有权
if (_ptr != ptr) {
delete _ptr;
}
_ptr = ptr;
}
T* get() const; //返回原始对象的指针
T* release(); //放弃指针的所有权 记住 release() 函数不会释放对象,仅仅归还所有权。
void reset(T* ptr = NULL);//删除原有指针并获得指针的p的所有权
private:
T* _Ptr;
};
上面有两点需要说明:
1,在对“*”进行重载时,如果返回值写成”T ”,而不是“T &”。若出现下面赋值语句:“*P1 = 12”,则编译不会通过。
2,在对“->”进行重载时, 对这条语句:”P1->A = 30” //P1.operator->()->A = 30; 即P1->->A;但是这个可读性太差,编译器进行了优化,P1->A;
总结:std::auto_ptr 可用来管理单个对象的内存,但是,请注意如下几点:
1) 首先auto_ptr智能指针是个封装好的类;
2) 尽量不要使用“operator=”。如果使用了,请不要再使用先前对象;
3) std::auto_ptr 最好不要当成参数传递(读者可以自行写代码确定为什么不能);
4) 采用栈上的指针去管理堆上的内容,所以auto_ptr所管理的对象必须是new出来的,也不能是malloc出来的。(原因:在auto_ptr的实现机制中,采用的是delete 掉一个指针,该delete一方面是调用了指针所指对象的析构函数(这也是为什么采用智能指针,new了一个对象,但是不用delete的原因),另一方面释放了堆空间的内存。)
使用场景总结:
1)不要使用auto_ptr对象保存指向静态分配对象的指针,否则,当auto_ptr对象本身被撤销的时候,它将试图删除指向非动态分配对象的指针,导致未定义的行为。
2)永远不要使用两个 auto_ptrs 对象指向同一对象,导致这个错误的一种明显方式是,使用同一指针来初始化或者 reset 两个不同的 auto_ptr对象。另一种导致这个错误的微妙方式可能是,使用一个 auto_ptr 对象的 get 函数的结果来初始化或者 reset另一个 auto_ptr 对象。(每个智能指针对象析构的时候,都会调用一次delete,导致堆空间内存被释放两次,然而这是不被允许的。)
3)不要使用 auto_ptr 对象保存指向动态分配数组的指针。当auto_ptr 对象被删除的时候,它只释放一个对象—它使用普通delete 操作符,而不用数组的 delete [] 操作符。
4)不要将 auto_ptr 对象存储在容器中。容器要求所保存的类型定义复制和赋值操作符,使它们表现得类似于内置类型的操作符:在复制(或者赋值)之后,两个对象必须具有相同值,auto_ptr 类不满足这个要求。
使用一个 std::auto_ptr 的限制很多,还不能用来管理堆内存数组,如此多的限制就很容易导致问题。所以说它是一个带有缺陷的设计,是一个“弃儿”。由于 std::auto_ptr 引发了诸多问题,一些设计并不是非常符合 C++ 编程思想,所以C++引入了下面 boost 库的智能指针,boost 智能指针可以解决如上问题。
2.2 boost::scoped_ptr
boost库发展的简单介绍:在C++11标准出来之前,C++98标准中都一直只有一个智能指针auto_ptr,我们知道,这是一个失败的设计。它的本质是管理权的转移,这有许多问题。而这时就有一群人开始扩展C++标准库的关于智能指针的部分,他们组成了boost社区,他们负责boost库的开发和维护。其目的是为C++程序员提供免费的、同行审查的、可移植的程序库。boost库可以和C++标准库完美的共同工作,并且为其提供扩展功能。现在的C++11标准库的智能指针很大程度上“借鉴”了boost库。
boost::scoped_ptr 属于 boost 库,定义在 namespace boost 中,包含头文件#include<boost/smart_ptr.hpp> 便可以使用。scoped_ptr 跟 auto_ptr 一样,可以方便的管理单个堆内存对象,特别的是,scoped_ptr 独享所有权,避免了auto_ptr恼人的几个问题。
scoped_ptr是一种简单粗暴的设计,它本质就是防拷贝,避免出现管理权的转移。这是它的最大特点,所以他的拷贝构造函数和赋值运算符重载函数都只是声明而不定义,而且为了防止有的人在类外定义,所以将函数声明为protected。但这也是它最大的问题所在,就是不能赋值拷贝,也就是说功能不全。但是这种设计比较高效、简洁。没有 release() 函数,不会导致先前的内存泄露问题。下面我也将模拟实现scoped_ptr的管理机制:
template<class T>
class ScopedPtr
{
public:
ScopedPtr(T* ptr = NULL) {
:_ptr(ptr)
cout << "ScopedPtr()" << end;
}
~ScopedPtr(){
delete _ptr;
cout << "~ScopedPtr()" << end;
}
T& operator* (){
return *_ptr;
}
T* operator->() {
return _ptr;
}
bool operator==(const ScopedPtr<T>& sp) {
return _ptr == sp._ptr;
}
bool operator!=(const ScopedPtr<T>& sp) {
return _ptr != sp._ptr;
}
void Reset(T* ptr = NULL)
{
if (_ptr != ptr)
{
delete _ptr;
}
_ptr = ptr;
}
protected:
ScopedPtr(ScopedPtr<T>& sp); //防拷贝(只声明不定义,为防止别人在类外定义,就将他声明为protected)
ScopedPtr<T>& operator=(ScopedPtr<T>& sp);
private:
T* _ptr;
};
scoped_ptr使用特点总结:
1)与auto_ptr类似,采用栈上的指针去管理堆上的内容,从而使得堆上的对象随着栈上对象销毁时自动删除;
2)scoped_ptr有着更严格的使用限制——不能拷贝,这也意味着scoped_ptr不能转换其所有权,所以它管理的对象不能作为函数的返回值,对象生命周期仅仅局限于一定区间(该指针所在的{}区间,而std::auto_ptr可以);
3)由于防拷贝的特性,使其管理的对象不能共享所有权,这与std::auto_ptr类似,这一特点使该指针简单易用,但也造成了功能的薄弱。
C++智能指针剖析(上)std::auto_ptr与boost::scoped_ptr的更多相关文章
- 智能指针剖析(上)std::auto_ptr与boost::scoped_ptr
1. 引入 C++语言中的动态内存分配没有自动回收机制,动态开辟的空间需要用户自己来维护,在出函数作用域或者程序正常退出前必须释放掉. 即程序员每次 new 出来的内存都要手动 delete,否则会造 ...
- c++智能指针《一》 auto_ptr
转载http://www.cnblogs.com/gnagwang/archive/2010/11/19/1881811.html C++的auto_ptr auto_ptr所做的事情,就是动态分配对 ...
- 【C++】智能指针详解(二):auto_ptr
首先,我要声明auto_ptr是一个坑!auto_ptr是一个坑!auto_ptr是一个坑!重要的事情说三遍!!! 通过上文,我们知道智能指针通过对象去管理指针,在构造对象时完成资源的分配及初始化,在 ...
- 智能指针剖析(下)boost::shared_ptr&其他
1. boost::shared_ptr 前面我已经讲解了两个比较简单的智能指针,它们都有各自的优缺点.由于 boost::scoped_ptr 独享所有权,当我们真真需要复制智能指针时,需求便满足不 ...
- C++智能指针剖析(下)boost::shared_ptr&其他
1. boost::shared_ptr 前面我已经讲解了两个比较简单的智能指针,它们都有各自的优缺点.由于 boost::scoped_ptr 独享所有权,当我们真真需要复制智能指针时,需求便满足不 ...
- 智能指针思想实践(std::unique_ptr, std::shared_ptr)
1 smart pointer 思想 个人认为smart pointer实际上就是一个对原始指针类型的一个封装类,并对外提供了-> 和 * 两种操作,使得其能够表现出原始指针的操作行为. ...
- 聊聊 C++ 中的几种智能指针 (上)
一:背景 我们知道 C++ 是手工管理内存的分配和释放,对应的操作符就是 new/delete 和 new[] / delete[], 这给了程序员极大的自由度也给了我们极高的门槛,弄不好就得内存泄露 ...
- 智能指针 auto_ptr、scoped_ptr、shared_ptr、weak_ptr
什么是RAII? RAII是Resource Acquisition Is Initialization的简称,是C++语言的一种管理资源.避免泄漏的惯用法. RAII又叫做资源分配即初始化,即:定义 ...
- C++11智能指针的深度理解
平时习惯使用cocos2d-x的Ref内存模式,回过头来在控制台项目中觉得c++的智能指针有点生疏,于是便重温一下.首先有请c++智能指针们登场: std::auto_ptr.std::unique_ ...
随机推荐
- app Inventor
什么是App Inventor ? MIT 官方网站 http://ai2.appinventor.mit.edu/Ya_tos_form.html 广州中文镜像网站 http://app.gzjk ...
- [LVM]创建LVM卷
https://www.cnblogs.com/softidea/p/5147090.html
- [NOIP2017] 列队(平衡树)
考虑转化题意: 设这次操作删掉点\((x, y)\) 对于每一次向左看齐:在第x行删除\((x, y)\),并将y以后的点全部前移一位 对于每一次向前看齐:x以后的点全部上移一位,并在最后一列插入\( ...
- C# 中的#if、#elif、#else、#endif等条件编译符号
C#编译器遇到一个由#if和#endif包围起来的语句块时,会检查#if后面的符号是否已经被定义了,如果已经被定义,那么才会编译语句块之间的代码.而定义一个可以被#if测试的符号需要事先用#defin ...
- 支付宝aar添加与友盟冲突解决
Program type already present: com.ta.utdid2.b.a.e" 错误提示: 删掉libs中utdid的jar.
- eclipse Tomcat 容器已经启动 但右下角 progress 一直显示100%
今天在家里 遇到一个问题 先上图 我的默认超时时间 eclipse 默认的 45s 果然 到了时间 依旧是 显示超时 解决方法其实 很简单 我看到网上有人说是tomcat 或者 eclip ...
- C语言博客05--指针
C语言博客05--指针 1.本章学习总结 1.1 思维导图 1.2 本章学习体会及代码量学习体会 1.2.1 学习体会 在本周的学习过程中,我们学习了指针的用法.说实话,指针的用法有点绕,之前一直没搞 ...
- 一文了解Python的线程
问题 什么是线程? 如何创建.执行线程? 如何使用线程池ThreadPoolExecutor? 如何避免资源竞争问题? 如何使用Python中线程模块threading提供的常用工具? 目录 1. 什 ...
- c语言变量及输入输出
scanf: 格式字符串的一般形式:%[*][输入数据宽度][长度] 类型 (其中有方括号[] 的项为任选项.) 各项意义: 1) 类型:表示输入数据的类型,其格式符和意义如下表所示. ...
- [Deep Learning] 深度学习中消失的梯度
好久没有更新blog了,最近抽时间看了Nielsen的<Neural Networks and Deep Learning>感觉小有收获,分享给大家. 了解深度学习的同学可能知道,目前深度 ...