C++ 浅析智能指针
Posted Readtears
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ 浅析智能指针相关的知识,希望对你有一定的参考价值。
引言:
由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete。程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见。
RAII(Resource Acquisition Is Initialization)
资源分配即初始化,定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。
所谓智能指针就是智能/自动化的管理指针所指向的动态资源的释放。
STL--auto_ptr
Boost库的智能指针(ps:新的C++11标准中已经引入了unique_ptr/shared_ptr/weak_ptr)
在这里,对于aut_optr,scoped_ptr , shared_ptr 进行剖析
一. aut_optr
std::auto_ptr 属于 STL,当然在 namespace std 中,包含头文件 #include<memory> 便可以使用。std::auto_ptr 能够方便的管理单个堆内存对象。为了解决单个对象被重复释放多次的情况,我们使用的aut_optr的目的就是转移权限,顾名思义就是说再拷贝构造新的对象时,由于此时有两个对象指向同一块空间,所以将原来的指针赋NULL,然后将这块空间的所有权交给新的对象指针。
对应代码:<AutoPtr>
#include<iostream>
using namespace std;
template<class T>
class AutoPtr
public:
AutoPtr(T* ptr)
:_ptr(ptr)
AutoPtr()
:_ptr(NULL)
AutoPtr<T>(AutoPtr<T>& ap) //权限转移
: _ptr(ap._ptr)
ap._ptr = NULL;
AutoPtr<T>& operator=(AutoPtr<T>& ap)
if (&ap != this)
delete _ptr;
_ptr = ap._ptr;
ap._ptr = NULL; //权限转移
return *this;
~AutoPtr()
if (_ptr)
delete _ptr;
_ptr = NULL;
T& operator*()
return *_ptr;
private:
T* _ptr;
;
void Test()
AutoPtr<int> ap1(new int(2));
AutoPtr<int> ap2 = ap1;
AutoPtr<int> ap3(new int(3));
ap3 = ap1;
int main()
Test();
return 0;
虽然表面上解决了重复释放的问题,但是却存在一个很大问题,请看右图:
(先忽略 s 的存在)
若有s2对象,指向自己的一块空间,现在要拷贝构造一个s3对象,那么顺应上面的代码,s2会被置NULL,而这块空间重归s3所管理。
如果考虑到这块空间已经有多个指针指向,即s和s2,那么再拷贝构造s3时,s2被置空,此时,s变成垂悬指针。这就是一个打的问题所在!
二. scoped_ptr
实用的智能指针,思想就是防拷贝,在大多时候用不到拷贝构造和赋值运算符重载,那么我们做的就是写出构造函数和析构函数,拷贝构造和赋值运算符重载只声明不定义。这里有几点要说明:
<1>在代码中,将拷贝构造和赋值运算符重载设置成保护或者私有的,为什么?世界之大,总有些人想害朕。。话归主题,原因是防止有坏人在外面修改,倘若被修改也不会调用修改的,因为无法访问得到。
<2>为什么要声明拷贝构造和赋值函数,原因是如果不声明,编译器会调用自己默认的构造和赋值函数,那么防拷贝就无从谈起。
代码如下:<ScopedPtr>
#include<iostream>
using namespace std;
template<class T>
class ScopedPtr
public:
ScopedPtr(T* ptr)
:_ptr(ptr)
Scoped()
:_ptr(NULL)
~ScopedPtr()
if (_ptr)
delete _ptr;
_ptr = NULL;
T& operator*()
return *_ptr;
T* operator->()
return _ptr;
T* GetPtr()
return _ptr;
protected:
ScopedPtr<T>(const ScopedPtr<T>& sp);// 只声明不定义,防拷贝
ScopedPtr<T>& operator = (const ScopedPtr<T>& sp);
private:
T* _ptr;
;
void Test()
ScopedPtr<int> sp1(new int(2));
ScopedPtr<int> sp2 = sp1;
ScopedPtr<int> sp3(new int(3));
sp3 = sp1;
int main()
Test();
return 0;
auto_ptr和scopedptr的取舍:
boost::scoped_ptr 用于确保动态分配的对象能够被正确地删除。scoped_ptr 有着与std::auto_ptr类似的特性,而最大的区别在于它不能转让所有权而auto_ptr可以。事实上,scoped_ptr永远不能被复制或被赋值!scoped_ptr 拥有它所指向的资源的所有权,并永远不会放弃这个所有权。scoped_ptr的这种特性提升了我们的代码的表现,我们可以根据需要选择最合适的智能指针(scoped_ptr 或 auto_ptr)。要决定使用std::auto_ptr还是boost::scoped_ptr, 就要考虑转移所有权是不是你想要的智能指针的一个特性。如果不是,就用scoped_ptr. 它是一种轻量级的智能指针;使用它不会使你的程序变大或变慢。它只会让你的代码更安全,更好维护。
三. sharede_ptr
在上面我们看到 boost::scoped_ptr 独享所有权,不允许赋值、拷贝,boost::shared_ptr 是专门用于共享所有权的,由于要共享所有权,其在内部使用了引用计数。boost::shared_ptr 也是用于管理单个堆内存对象的。
这个智能指针解决了auto_ptr独占的问题,采用引用计数的方法,一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。这在非环形数据结构中防止资源泄露很有帮助。
用法:删除共用对象
对应代码:<SharedPtr>
#include<iostream>
using namespace std;
template<class T>
class SharedPtr
public:
SharedPtr(T* ptr)
:_ptr(ptr)
, _pCount(new long(1))
SharedPtr()
:_ptr(NULL)
, _pCount(new long(1))
SharedPtr<T>(const SharedPtr<T>& sp)
: _ptr(sp._ptr)
, _pCount(sp._pCount)
++(*_pCount);
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
if (&sp != this)
if (--(*_pCount) == 0) // 减到0释放对象一次
delete _ptr;
delete _pCount;
_ptr = sp._ptr;
_pCount = sp._pCount;
++(*_pCount);
return *this;
~SharedPtr()
if (_ptr)
if (--(*_pCount) == 0)
delete _ptr;
delete _pCount;
T& operator*()
return *_ptr;
T* operator->()
return _ptr;
long GetCount()
return *(_pCount);
T* GetPtr()
return _ptr;
private:
T* _ptr;
long* _pCount;
;
void Test()
SharedPtr<int> sp1 = new int(1);
SharedPtr<int> sp2 = sp1;
SharedPtr<int> sp3 = new int(2);
sp3 = sp1;
int main()
Test();
return 0;
但是shared_ptr看起来完美,但是也存在一下问题:
1. 引用计数更新存在着线程安全
2.循环引用
3.定置删除器
我们先在这里讨论第二种情况,即循环引用问题
我们引出循环引用的场景图:
此时,两个节点的引用计数都为2,因为有两个指针分别指向a和b,此时就有了一个问题,_next析构时等着_prev析构,_prev要析构,只能b析构;而_prev也在等_next析构,_next要析构,只能a析构。这样就有个问题类似”你等我,我等你,无休止,永远不会析构,一直循环“。
要解决这个问题,就又要引出弱指针:weak_ptr
weak_ptr唯一的功能就是解决shared_ptr的循环引用问题,那是怎么解决的呢?
我们看代码:
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
using namespace boost;
struct ListNode
shared_ptr<ListNode > _prev;
shared_ptr<ListNode > _next;
//weak_ptr<ListNode > _prev; // 在此,weak_ptr可以解决循环引用的问题
//weak_ptr<ListNode > _next;
~ ListNode()
cout<<"~ListNode()" <<endl;
;
void Test ()
// 循环引用问题
shared_ptr <ListNode > p1( new ListNode ());
shared_ptr <ListNode > p2( new ListNode ());
cout <<"p1->Count:" << p1. use_count()<<endl ;// use_count是库里的引用计数
cout <<"p2->Count:" << p2. use_count()<<endl ;
// p1节点的_next指向 p2节点
p1->_next = p2;
// p2节点的_prev指向 p1节点
p2->_prev = p1;
cout <<"p1->Count:" << p1. use_count ()<<endl ;
cout <<"p2->Count:" << p2. use_count ()<<endl ;
这样问题就得到了解决。
至此,我们也知道了,当需要写一个智能指针时,我们尽可能的去写scoped_ptr或者shared_ptr,而千万不要写auto_ptr,问题上面已经详细分解。
偏文不到之处,还请评正。
本文出自 “Vs吕小布” 博客,转载请与作者联系!
以上是关于C++ 浅析智能指针的主要内容,如果未能解决你的问题,请参考以下文章