本文根据众多互联网博客内容整理后形成,引用内容的版权归原始作者所有,仅限于学习研究使用。
1 std::auto_ptr
1.1 auto_ptr介绍
智能指针能保证,无论在何种情况下,只要自己被摧毁,就一定连带释放其所指资源。auto_ptr是这样的一种指针:它是“它所指向的对象”的拥有者。auto_ptr要求一个对象只能有一个拥有者,严禁一物二主。不再需要delete,也不再需要catch了。不用担心忘掉delete动作,担心程序异常结束时内存遗失或者资源遗失,只要有智能指针auto_ptr.
c++标准库提供的auto_ptr一种帮助程序员防止“被抛出异常时发生资源泄漏”的智能指针。
auto_ptr在头文件#include<memory> 中
auto_ptr没有所有的指针算术(包括++)运算。auto_ptr不允许你使用一般指针惯用的赋值初始化方式。 例如:
std::auto_ptr<ClassA> ptr1(new ClassA); //对的
std::auto_ptr<ClassA> ptr2 =new ClassA; //错的
2.auto_ptr拥有权的转移
std::auto_ptr<ClassA> ptr1(new ClassA);//ptr1拥有了那个new出来的对象。
std::auto_ptr<ClassA> ptr2(ptr1); //ptr2拥有了那个new出来的对象,ptr1=NULL不再拥有它,即拥有权的转移;
//这样对象就只会在ptr2被摧毁的时候被delete一次。
下面的赋值操作和上面的差不多:
std::auto_ptr<ClassA> ptr1(new ClassA);
std::auto_ptr<ClassA> ptr2;
ptr2=ptr1; //拥有权从ptr1转移到ptr2
如果ptr2被赋值之前正拥有另一个对象,赋值时将会delete掉那个对象。
std::auto_ptr<ClassA> ptr1(new ClassA);
std::auto_ptr<ClassA> ptr2(new ClassA);
ptr2=ptr1; //拥有权从ptr1转移到ptr2,ptr2的原来的对象被delete掉。
拥有权的转移,实质上并非只是被简单拷贝而已。只要发生了拥有权转移,先前的拥有者就失去了拥有权,结果拥有者一旦交出拥有权,就两手空空,只剩下一个null指针在手了。
只有auto_ptr可以拿来当做另一个auto_ptr的初值,普通指针是不行的。例如:
std::auto_ptr<ClassA> ptr ;//可以定义空的auto_ptr指针,对于智能指针,因为构造函数有默认值0
ptr =new ClassA; //错误,
ptr =std::auto_ptr<ClassA> (new ClassA);//正确被auto_ptr对象或者auto_ptr指针赋值,
3)某函数是数据的终点,auto_ptr以传值方式被当作一个参数传递给某函数时。被调用端的参数获得了这个auto_ptr的拥有权,如果函数不再将它传递出去,它所指的对象就会在函数退出时被删除。如:void sink(std::auto_ptr<ClassA> );
4) 某函数是数据的起点。
std::auto_ptr<ClassA> f()
{
std::auto_ptr<ClassA> ptr(new ClassA);
//........
return ptr;
}
void g()
{
std::auto_ptr<ClassA> p;
for (int i =0; i <10; i++) {
p=f(); //p获得f()返回对象的拥有权。每当f()被调用时,它都new一个对象,然后把该对象连同其拥有权一起返回给调用端。
//一旦循环再次执行这个赋值动作,p原先拥有的对象被删除。当离开g()时,p也被摧毁。
}
}
auto_ptr的语义本身就包含了拥有权,所以如果你无意转交你的拥有权,就不要在参数列表值中使用auto_ptr,也不要以它作为返回值。
下面的例子本来是想将auto_ptr所指对象的值打印出来,却可能引发了错误:
//a bad example
#include <iostream>
#include <memory>
using namespace std;
template<class T>
void bad_print(std::auto_ptr<T> p)
{
if (NULL ==p.get())
cout <<"NULL";
else
cout << *p;
}
int main()
{
auto_ptr<int> p(new int);
*p =42;
bad_print(p); //bad_print调用结束后,p所指向的对象被删除了。
*p =18;//执行期错误,用g++编译时出现Segmentation fault (core dumped)(段错误)
return 0;
}
//a bad example
#include <iostream>
#include <memory>
using namespace std;
template<class T>
void bad_print(std::auto_ptr<T> p)
{
if (NULL ==p.get())
cout <<"NULL";
else
cout << *p;
}
int main()
{
const auto_ptr<int> p(new int);
*p =42;
bad_print(p); //编译期错误,无法变更constant reference的拥有权
*p =18; //ok
return 0;
}
关键字const并非意味你不能更改auto_ptr所拥有的对象,而是意味着你不能更改auto_ptr的拥有权。例如:
std::auto_ptr<int> f()
{
const std::auto_ptr<int> p(new int);
std::auto_ptr<int> q(new int);
*p =42;//ok,改变值
bad_print(p);//编译期错误,不能变更拥有权
*p =*q; //ok;
p =q; //编译期错误
return p; //编译期错误
}
如果使用const auto_ptr作为参数,对新对象的任何赋值操作都将导致编译期错误。就其常数特性而言,const auto_ptr比较类似常数指针(T* const p),
而非指向常数的指针(const T* p),尽管语法上更像后者。
1.3 auto_ptr的正确运用:
1)auto_ptr之间不能共享拥有权,即不要让两个auto_ptr指向同一个对象。
一个auto_ptr千万不能指向另一个auto_ptr(或其它对象)所拥有的对象,尽管语法上没问题,很多情况下可能也不会发生错误。但是,当一个指针删除该对象后,另一个指针突然间指向一个已被摧毁的对象,那么,如果再使用那个指针进行读写操作,就会引发一场灾难。
2)并不存在针对array而设计的auto_ptrs
auto_ptr不能指向array,因为auto_ptr是透过delete而非delete[ ]来释放其所拥有的对象。注意,c++标准程序库并未针对array而设计的auto_ptr。标准程序库另提供了数个容器类别,用来管理数据群。
3)auto_ptrs绝非一个“四海通用”的智能型指针
并非任何适用智能指针的地方都适用auto_ptr,特别请注意的是,它不是引用计数型指针——这种指针保证,如果有一组智能型指针指向同一个对象,那么当且仅当最后一个智能型指针被销毁时,该对象才会被销毁。
4)auto_ptrs不满足STL容器对其他元素的要求
auto_ptr并不满足STL标准容器对于元素的最基本要求,因为在拷贝和赋值动作之后,原本的auto_ptr和新产生的auto_ptr并不相等。是的,拷贝和赋值之后,原本的auto_ptr会交出拥有权,而不是拷贝给新的auto_ptr。因此绝对不要将auto_ptr作为标准容器的元素。
5)不要使用auto_ptr对象保存指向静态分配对象的指针,否则,当auto_ptr对象本身被撤销的时候,它将试图删除指向非动态分配对象的指针,导致未定义的行为。
使用一个 std::auto_ptr 的限制很多,还不能用来管理堆内存数组,如此多的限制就很容易导致问题。所以说它是一个带有缺陷的设计,是一个“弃儿”。由于 std::auto_ptr 引发了诸多问题,一些设计并不是非常符合 C++ 编程思想,所以C++引入了下面 boost 库的智能指针,boost 智能指针可以解决如上问题。
1.4 总结:
std::auto_ptr 可用来管理单个对象的内存,但是,请注意如下几点:
1) 首先auto_ptr智能指针是个封装好的类;
2) 尽量不要使用“operator=”。如果使用了,请不要再使用先前对象;
3) std::auto_ptr 最好不要当成参数传递(读者可以自行写代码确定为什么不能);
4) 采用栈上的指针去管理堆上的内容,所以auto_ptr所管理的对象必须是new出来的,也不能是malloc出来的。(原因:在auto_ptr的实现机制中,采用的是delete 掉一个指针,该delete一方面是调用了指针所指对象的析构函数(这也是为什么采用智能指针,new了一个对象,但是不用delete的原因),另一方面释放了堆空间的内存。)
2 boost::scoped_ptr
2.1 介绍
boost::scoped_ptr和std::auto_ptr非常类似,是一个简单的智能指针,它能够保证在离开作用域后对象被自动释放。下列代码演示了该指针的基本应用:
#include <string> |
该代码的输出结果是:
Test Begin ... |
可以看到:当implementation类离其开impl作用域的时候,会被自动删除,这样就会避免由于忘记手动调用delete而造成内存泄漏了。
2.2 boost::scoped_ptr特点:
boost::scoped_ptr 属于 boost 库,定义在 namespace boost 中,包含头文件#include<boost/smart_ptr.hpp> 便可以使用。scoped_ptr 跟 auto_ptr 一样,可以方便的管理单个堆内存对象,特别的是,scoped_ptr 独享所有权,避免了auto_ptr恼人的几个问题。
scoped_ptr是一种简单粗暴的设计,它本质就是防拷贝,避免出现管理权的转移。这是它的最大特点,所以他的拷贝构造函数和赋值运算符重载函数都只是声明而不定义,而且为了防止有的人在类外定义,所以将函数声明为protected。但这也是它最大的问题所在,就是不能赋值拷贝,也就是说功能不全。但是这种设计比较高效、简洁。没有 release() 函数,不会导致先前的内存泄露问题。
boost::scoped_ptr的实现和std::auto_ptr非常类似,都是利用了一个栈上的对象去管理一个堆上的对象,从而使得堆上的对象随着栈上的对象销毁时自动删除。不同的是,boost::scoped_ptr有着更严格的使用限制——不能拷贝。这就意味着:boost::scoped_ptr指针是不能转换其所有权的。
-
不能转换所有权
boost::scoped_ptr所管理的对象生命周期仅仅局限于一个区间(该指针所在的"{}"之间),无法传到区间之外,这就意味着boost::scoped_ptr对象是不能作为函数的返回值的(std::auto_ptr可以)。 -
不能共享所有权
这点和std::auto_ptr类似。这个特点一方面使得该指针简单易用。另一方面也造成了功能的薄弱——不能用于stl的容器中。 -
不能用于管理数组对象
由于boost::scoped_ptr是通过delete来删除所管理对象的,而数组对象必须通过deletep[]来删除,因此boost::scoped_ptr是不能管理数组对象的,如果要管理数组对象需要使用boost::scoped_array类。
2.3 boost::scoped_ptr的常用操作:
可以简化为如下形式:
namespace boost { |
它的常用操作如下:
成员函数 | 功能 |
operator*() | 以引用的形式访问所管理的对象的成员 |
operator->() | 以指针的形式访问所管理的对象的成员 |
reset() | 释放所管理的对象,管理另外一个对象 |
swap(scoped_ptr& b) | 交换两个boost::scoped_ptr管理的对象 |
下列测试代码演示了这些功能函数的基本使用方法。
#include <string> |
2.4 scoped_ptr使用特点总结:
1)与auto_ptr类似,采用栈上的指针去管理堆上的内容,从而使得堆上的对象随着栈上对象销毁时自动删除;
2)scoped_ptr有着更严格的使用限制——不能拷贝,这也意味着scoped_ptr不能转换其所有权,所以它管理的对象不能作为函数的返回值,对象生命周期仅仅局限于一定区间(该指针所在的{}区间,而std::auto_ptr可以);
3)由于防拷贝的特性,使其管理的对象不能共享所有权,这与std::auto_ptr类似,这一特点使该指针简单易用,但也造成了功能的薄弱。
3 boost::scoped_ptr和std::auto_ptr的选取:
boost::scoped_ptr和std::auto_ptr的功能和操作都非常类似,如何在他们之间选取取决于是否需要转移所管理的对象的所有权(如是否需要作为函数的返回值)。如果没有这个需要的话,大可以使用boost::scoped_ptr,让编译器来进行更严格的检查,来发现一些不正确的赋值操作。
参考:
https://www.cnblogs.com/TianFang/archive/2008/09/15/1291050.html
https://www.cnblogs.com/ckings/p/3663028.html
https://www.cnblogs.com/33debug/p/6832726.html