模拟实现Boost库智能指针auto_ptr

2017-04-03 13:44 阅读 725 次 评论 1 条

C++提供了4种智能指针用于对分配的内存进行自动释放,分别是auto_ptr、scope_ptr、shared_ptr、weak_ptr。其中auto_ptr在C++98标准引入,后三种在C++11标准中加入。但是auto_ptr已经被C++摒弃,不建议使用。

为什么引入智能指针

①在C++中,如果使用普通指针来创建一个指向某个对象的指针,那么在使用完这个对象之后我们需要自己释放它,如果忘记释放,那么会造成一个悬挂指针,也就是说这个指针现在指向的内存区域其内容程序员无法把握和控制,就很可能造成内存泄漏。

②还记得C++的异常机制吗,在析构函数中是不能抛出异常的,否则可能导致资源泄漏 (内存泄漏、句柄未关闭等)。

因此智能指针的出现实际上就是为了可以方便的控制对象的生命期,在智能指针中,一个对象什么时候和在什么条件下要被析构或者是删除是受智能指针本身决定的,用户并不需要管理。

智能指针的功能

智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。

auto_ptr智能指针

auto_ptr的核心思想是资源管理权的转移,即在构造对象时赋予资源管理权,析构对象时撤销管理权,但是auto_ptr尽量在任何情况下都不要使用,资源管理权的转移带来的最重要的缺点就是安全性问题,下面具体阐述一下新旧版本的auto_ptr方便理解。

模拟实现旧版的auto_ptr

既然要控制资源的转移,我们可以多定义一个成员对象_owner来标识该成员对象的资源管理权,在调用构造函数创建对象时赋予其资源管理权,即_owner置为true,在调用拷贝构造函数、赋值运算符的时候将其管理权释放,即_owner置为false,就起到了资源管理权的转移。

下面写一个测试用例来明确一下auto_ptr的缺陷在哪里,比如下面这段程序:

p2出了if的作用域后会被释放,空间会回收,当再次试图给已经释放空间的对象赋值时,必然导致寻址失败导致程序奔溃,这时p1虚无定所,也就成了一个野指针。

模拟实现新版的auto_ptr

继承旧版aoto_ptr的资源管理权转移的思想,去除标识管理权的_owner,当我们在调用拷贝构造函数、赋值运算符后直接将原对象置为NULL,禁止其访问原来的内存空间。

既然不让用总得有些理由吧,下面这段测试代码就足以让程序奔溃:

这样的结果虽出乎意料,但也情理之中,对一个已经释放的对象重新赋值,必然导致程序奔溃,这样的程序是非常不安全的,到最后你会发现自己曾经种下的果树,作为所有者也无法吃到果子。

上述代码,FunTest()函数以值的形式返回,为一个临时对象,临时对象时具有常性的,因此在调用拷贝构造函数的时候,不能写成const T* ptr,const类型的赋值不能给非const类型,所以我们必须将赋值运算符重载与拷贝构造函数都设置为非const类型。

在VS2017下,这段程序是正常执行的,在new一个对象时产生了一个无名对象,无名对象是具有常性的,在拷贝构造无名对象时,拷贝构造函数应该用const类型的参数来接收,但是这样的程序却成功运行了,原因就在于VS2017下编译器对其进行了优化,即不会再去调用它的拷贝构造函数,而是直接取调用构造函数。

为了验证这个程序的跨平台效果,我决定在g++上测试一下程序的正确性:

我们发现在g++上测试编译阶段都过不了,用一个具有常性的对象去拷贝构造一个对象时,必须用const类型对象的引用来接收具有const性质的的对象。

auto_ptr的总结

说了这么多,其实最终目的就是千万别用这个已经被C++摈弃的库函数,资源权限的转移带来的是安全性的问题,当你理解了它的使用带来的众多内存泄漏,程序崩溃的实例时,相信你对这个智能指针也必须保持敬畏之心了。

版权声明:本文著作权归原作者所有,欢迎分享本文,谢谢支持!
转载请注明:模拟实现Boost库智能指针auto_ptr | 术与道的分享
分类:编程素养 标签:,
1024do.com导航_术与道导航平台

发表评论


表情