引用计数我不怕之智能指针

前言

使用引记数,就算是再历害的高手也难免会出错。而一但出错了,之后再去查问题可就相当的困难了。正如我曾经看到,有一段代码是这样的:

m_spView->Release();
m_spView->Release();
m_spView->Release();

 

看到这段代码,就知道引用计数出问题了。他想通过这种方式,把多出来的计数Release掉。但这么做能解决问题吗?答案是不能,这样的代码还可能造成严重的稳定性问题。解决引用计数问题,除了要了解引用计数规则外,我们还提昌要用智能指针。智能能帮助我们很好的处理引用计数问题。

 

智能指针的差异

在用VC开发应用程序时,有两个引用计数类可供我们使用。_com_ptr_t与CComPtr,它们都能很好的帮助我们解决引用计数处理。但这两个类还是有一点小小的区别,有的时候这一点区别也是致命的,因此我们必须清楚它们的差别。下面我罗列了它们之间的差别:

  1. CComPtr的&运算符不会释放原指针,而_com_ptr_t会释放原指针。
  2. CComPtr对AddRef与Release做了限制,也就是不充许调用这两个方法,而_com_ptr_t并没有限制。
  3. CComPtr只能接受模版参数指定的指针,_com_ptr_t可以接受任何接口指针,并自动调用QueryInterface得到模板参数指定的指针类型。

这些区别,导致了有些代码不能同时应用于两个智能指针。

 

&运算符差异带来的风险

HRESULT hr GetView(int i, /*out*/IView** ppView)
{
    *ppView = m_Views[i];
    (*ppView)->AddRef();


return S_OK;
}…
}


CComPtr<IView> spView;
for (int i = 0; i < 10; i++)
{
    GetView(i, &spView);
spView->

以上代码会导致引用计数出错,前面的9个View的引用计数并没有Release。CComPtr<IView>的&运算符,会返回IView**也就是CComPtr内部成员的地址,但它不释放原来的指针。而GetView又会修改指针,直接把原来的指针抛弃了。

这个代码可以这样改:

for (int i = 0; i < 10; i++)
{
    CComPtr<IView> spView;
    GetView(i, &spView);
    spView->…
}

把指针作为循环的局部变量,这样每次循环退出前spView都会被析构,最终调用Release。当然还能这样改:

COM_SMARTPTR_TYPEDEF(IView, __uuidof(IView));
IView Ptr spView;
for (int i = 0; i < 10; i++)
{
    GetView(i, &spView);
    spView->…
}

_com_ptr_t的&运算符会帮助我们把原来的指针Release掉,所以我们就不必担心引用计数没有释放。

 

禁用AddRef与Release

然我们使用的智能指针,就不要再去调AddRef或Release了。如果再去手工调用它们,就失去了智能指针的好处。CComPtr有一个非常巧妙的方法,禁止调用这两个方法。它声明了一个类_NoAddRefReleaseOnCComPtr,它的定义如下:

template <class T>
class _NoAddRefReleaseOnCComPtr : public T
{
    private:
        STDMETHOD_(ULONG, AddRef)()=0;
        STDMETHOD_(ULONG, Release)()=0;
};

我们看到,里面就定义了两个私有函数。AddRef与Release,它们重写了IUnknown的这两个方法,并且继承自模板T。再来看段代码:

 

_NoAddRefReleaseOnCComPtr<T>* operator->() const
throw()
{
    ATLASSERT(p!=NULL);
    return (_NoAddRefReleaseOnCComPtr<T>*)p;
}

我们看到的是CComPtr的”->”运算符,它将内部的指针强制转换成_NoAddRefReleaseOnCComPtr<T>*。其中T是CComPtr的模板参数,也就是接口指针类型。可以看出_NoAddRefReleaseOnCComPtr<T>继承自接口类型,因此通过_NoAddRefReleaseOnCComPtr<T>*可以调用T的所有函数。前面我们看到NoAddRefReleaseOnCComPtr的两个私用函数,AddRef与Release,如果有谁想调用就会报编译错误。

 

自动QueryInterface

_com_ptr_t有多个=运算符版本,代码如下:

template<typename _IIID> class _com_ptr_t
{
public:
typedef _IIID ThisIIID;
typedef
typename _IIID::Interface Interface;

// Queries for interface.

template<typename _InterfaceType> _com_ptr_t& operator=(_InterfaceType* p)
{
HRESULT hr = _QueryInterface(p);

if (FAILED(hr) && (hr != E_NOINTERFACE)) {
_com_issue_error(hr);
}

return *this;
}

// Saves the interface.

template<> _com_ptr_t& operator=(Interface* pInterface) throw()
{
if (m_pInterface != pInterface) {
Interface* pOldInterface = m_pInterface;
m_pInterface = pInterface;
_AddRef();

if (pOldInterface != NULL) {
pOldInterface->Release();
}
}
return *this;
}

其中
template<typename _InterfaceType> _com_ptr_t& operator=(_InterfaceType* p)是一个模板函数,接受任意类型的指针,函数内部会调用传入参数”p”的QueryInterface。

template<> _com_ptr_t& operator=(Interface* pInterface) throw()是模板函数的一个偏特化版本,接受_com_ptr_t模板参数中指定的指针类型。当传入的接口指针类型,与类模板指定的类型一样时这个函数会被调用。它不需要做QueryInterface的调用,只是简单的AddRef;

综上所述,两个智能指针在同一份代码里混用,很可能导致不良后果。所以我认为,最好不要在同一份代码里混用。而这两个指针,我很喜欢_com_ptr_t,它在许多方面明显优于CComPtr。

Attach与Detach

使用了智能指针,也并不是高枕无忧了。它还是给我们带来了一些新的问题。

有一些第三方类库设计的不合理,它在函数的返回值里返回接口指针。如下代码就导会导致引用计数泄漏:

 

IView* GetView(int nIndex)
{
    IView* pView = m_Views[nIndex];

    pView->AddRef();

    return pView;
}

IViewPtr spView = GetView(0);

以上代码,注意调用GetView的地方。IViewPtr是智能指针,它的=运算符是会再调用AddRef而GetView里已经调了一次AddRef了,这里多了一次AddRef。别问我GetView中为什么要AddRef,这是引用计数规则,不清楚请看《引用计数我不怕之引用计数规则》。

解决这个问题的方法就是用Attach函数

IViewPtr spView;

spView.
Attach(GetView(0));

也许是有人写了Attach,而其它人不明白Attach的意思,结果写出了这样的代码。

void SetView(IView* pView)
{
    m_spView.Attach(pView);
}

根据引用计数规则,将指针保存为副本,必须AddRef。但是这个例子里没有这么干,结果m_spView变成了野指针。

前面我们看到的GetView很简单,但是下面我们要做一别的事情,于是要用智能指针。

HRESULT hr GetView(int nIndex, IView** ppView)
{
    IViewPtr spView = m_Views[nIndex];

    if (spView->IsVisable() != S_OK)
        return E_FAILD;

    *ppView = spView;

    return S_OK;
}

表面看来没什么问题,但在函数返回后,智能指针又会调用一次Release。要解决这个问题,可以调用Detach。

HRESULT hr GetView(int nIndex, IView** ppView)
{
    IViewPtr spView = m_Views[nIndex];

    if (spView->IsVisable() != S_OK)
        return E_FAILD; 

    *ppView = spView.
Detach();

    return S_OK;
}

Detach还是会被乱用,看到这样的代码还真是哭笑不得。

 

HRESULT hr ChangeView(int nIndex)
{
    IViewPtr spView = m_Views[nIndex].Detach();

    spView->Change();

    return S_OK;
}

这段代码能导致两个问题

  1. 引用计数泄漏
  2. m_Views中的指针变成空了

泄漏是由于Detach返回IView*,并不会Release,而spView又会再调用一次AddRef。智能指针的Detach是会把自己设成空的,否则还叫什么Detach。

使用智能指针,是解决引用计数问题最好的办法。不要因为用智能指针,会引入新的问题,而放弃使用它。只要花心思搞清楚智能指针的不同点,使用时注意一些细节问题,使用起来应该会变的非常轻松。

时间: 2024-10-25 03:24:40

引用计数我不怕之智能指针的相关文章

C++ 智能指针深入解析

以下是对C++中智能指针的使用进行了详细的分析介绍,需要的朋友可以参考下   1. 为什么需要智能指针?简单的说,智能指针是为了实现类似于Java中的垃圾回收机制.Java的 垃圾回收机制使程序员从繁杂的内存管理任务中彻底的解脱出来,在申请使用一块内存区域之后,无需去关注应该何时何地释放内存,Java将会自动帮助回收. 但是出于效率和其他原因(可能C++设计者不屑于这种傻瓜氏的编程方式),C++本身并没有这样的功能,其繁杂且易出错的内存管理也一直为广大程序员所诟 病. 更进一步地说,智能指针的出

智能指针(二):shared_ptr实现原理

前面讲到auto_ptr有个很大的缺陷就是所有权的转移,就是一个对象的内存块只能被一个智能指针对象所拥有.但我们有些时候希望共用那个内存块.于是C++ 11标准中有了shared_ptr这样的智能指针,顾名思义,有个shared表明共享嘛.所以shared_ptr类型的智能指针可以做为STL容器的元素   下面我们来瞧瞧shared_ptr具体是咋实现的.相较auto_ptr有下面几个不同的地方: 1.引进了一个计数器shared_count,用来表示当前有多少个智能指针对象共享指针指向的内存块

C++ 智能指针深入解析_C 语言

1. 为什么需要智能指针?简单的说,智能指针是为了实现类似于Java中的垃圾回收机制.Java的垃圾回收机制使程序员从繁杂的内存管理任务中彻底的解脱出来,在申请使用一块内存区域之后,无需去关注应该何时何地释放内存,Java将会自动帮助回收.但是出于效率和其他原因(可能C++设计者不屑于这种傻瓜氏的编程方式),C++本身并没有这样的功能,其繁杂且易出错的内存管理也一直为广大程序员所诟病. 更进一步地说,智能指针的出现是为了满足管理类中指针成员的需要.包含指针成员的类需要特别注意复制控制和赋值操作,

C++中智能指针如何设计和使用_C 语言

     智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露.它的一种通用实现技术是使用引用计数(reference count).智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针.每次创建类的新对象时,初始化指针并将引用计数置为1:当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数:对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用

指针辨析:悬垂指针、哑指针、野指针、智能指针

悬垂指针:   1:提出的原因: 请看下面的代码片段: int *p=NULL; void main() { int i=10;p=&i; cout<<"第一次:*p = "<<*p<<endl; cout<<"第二次:*p = "<<*p<<endl; } int *p=NULL; void fun() {int i=10;p=&i;} void main() { fun();

C++引用计数的智能指针有效回收方法

引用计数指针是否能有效地回收,对系统意外关机之后数据的恢复来说至关重要,关键是要避免对象复制. 怎样从灾难性故障中,恢复一个长期运行.系统级的后台守护进程或者服务,在如今的软件设计过程中,已成为了一个重要的考虑因素.当这些软件是由C++语言编成,并使用了引用计数的智能指针时,那么,智能指针的有效回收,对系统是否具有可伸缩级的恢复能力.甚至正确地继续未完成的操作来说,都显得至关重要. 在本文中,描述了一种方法,可从关机之后的软件恢复中,有效地回收引用计数指针,而且此方法在内存占用方面也非常高效,这

Chromium的智能指针/引用计数/Callback/Bind

这四个东西对使用者来说不难,看懂代码注释里的例子即可,预计1小时左右看懂全部.要去理解其设计思想的话最需要掌握的是模板类的使用,但一般使用者完全不用关心怎么设计的. 使用者的学习路径: 1.智能删除指针scoped_ptr 用作对普通指针的转储,防止忘记delete或不知道哪里delete.它跟引用计数没有关系. 头文件的注释就是使用示例 http://src.chromium.org/viewvc/chrome/trunk/src/base/memory/scoped_ptr.h templa

引用内部函数绑定机制,R转义字符,C++引用,别名,模板元,宏,断言,C++多线程,C++智能指针

 1.引用内部函数绑定机制 #include<iostream> #include<functional>   usingnamespacestd; usingnamespacestd::placeholders;   //仿函数,创建一个函数指针,引用一个结构体内部或者一个类内部的共有函数 structMyStruct {    voidadd(inta)    {        cout <<a <<endl;    }    voidadd2(in

智能指针与弱引用详解_Android

在android 中可以广泛看到的template<typename T> class Sp 句柄类实际上是android 为实现垃圾回收机制的智能指针.智能指针是c++ 中的一个概念,因为c++ 本身不具备垃圾回收机制,而且指针也不具备构造函数和析构函数,所以为了实现内存( 动态存储区) 的安全回收,必须对指针进行一层封装,而这个封装就是智能指针,其实说白了,智能指针就是具备指针功能同时提供安全内存回收的一个类.当然,智能指针的功能还不只这些,想了解更多大家可以去研究下- 智能指针有很多实现