有效的使用和设计COM智能指针——条款12:必要时使用attach() 和 detach()调整引用计数

26 篇文章 0 订阅
25 篇文章 1 订阅

条款12:必要时使用attach() 和 detach()调整引用计数

更多条款请前往原文出处:http://blog.csdn.net/liuchang5

假设我们使用了一个第三方编写的函数,或者它也是由我们编写的,但仅仅是由于起初没有使用智能指针。于是它的实现可能是如下这种样子的:

IView* GetView(int nIndex)
{
    IView* pView = m_Views[nIndex];
    pView->AddRef();             //引用计数被增加了一次。
    return pView;
}

这个函数没有问题,简单易懂。在传出参数的时候,还遵守了引用计数的三条规则从而调用了一次AddRef()。但我们如果这样使用它,情况会怎样呢?

void UseView(int nIndex)
{
  CComPtr<IView> spView = NULL;
  spView = GetView(0);         //错误的根源在这里
  spView->UserIt();
}

这再次出现了引用计数问题中最糟糕的结果——资源泄漏,且无声无息。

如果你没有发现上述引用计数的错误之处我们来“单步”看一下这一资源泄露问题是如何产生的:

1.首先在UseView中创建了一个智能指针。他指向为NULL;

2.调用GetView(0),他返回一个接口指针,并且增加了组件的引用计数。

3.GetView(0)通过智能指针重载的赋值运算符将接口指针赋值给智能指针,此时赋值运算符会增加一次引用技术。

4.通过智能指针使用接口。

5.智能指针出栈。引用计数递减一次。

认真查看上述过程,你会发现,引用技术增加了两次,而却只递减了一次。因此错误的根源在于智能指针重载的赋值运算符为我们多增加了一次引用计数。于是有人想到用下面这种方法来解决这一问题:

void UseView(int nIndex)
{
    CComPtr<IView> spView = NULL;
    spView = GetView(0);         //错误的根源在这里
    spView->Release();           //这样解决?很费解。
    spView->UserIt();
}

看到这样的代码你可能会问。Release()操作的理由在哪里? 为什么没有AddRef却单独出现了一个Release()。但如果你知晓这一过程的话,你会发现这样做的理由仅仅是为了抵消赋值运算符带来的一次副作用(多增加了引用计数)。但这样却给人造成理解上的困难,同时对于支持部分资源分配的接口,这里可能带来一些性能上的损失。

正确的解决方式是这样的:

void UseView(int nIndex)
{
    CComPtr<IView> spView = NULL;
    spView.Attach(GetView(0));        //OK,问题解决了。也很容易弄懂其意义。
    spView->UserIt();
}

利用智能指针提供的Attach操作可以将接口指针绑定到智能指针智能指针之上,而不增加其引用计数。再看看函数的运行过程,引用计数的增加和减少是不是平衡了?

或许你会说上面的GetView接口并不是十分符合COM设计的习惯,因为他没有用HRESULT作为返回值从而标识调用的成功与否。于是在你的新版本中你使用了HRESUTL作为返回值,用一个接口指针作为传出参数。最重要的是,你想到了通过智能指针来实现这个函数。这些想法都是很合理的,于是它看上去成了下面这个样子:

HRESULT hr GetView(int nIndex, IView** ppView)
{
    CComPtr<IView> spView = m_Views[nIndex];
    if (FAILED( spView->IsVisable()))
        return E_FAILD;
    *ppView = spView;   //欧~ 这里有出错了
    return S_OK;    
}

这又是一处潜在的让人难以琢磨的错误。如果你不能发现他,那我们来只能“单步”一次了。这可能会让你多动一动脑筋,但总比让程序崩掉或者内存泄漏要值得的多:

1.首先在UseView中创建了一个智能指针。他指向了一个COM接口,接口引用计数增加。

2.调用ISVisable(),并判断是否成功。若失败,则返回,此时智能指针被析构。引用计数递减。若成功,则进行下一步。

3.将智能指针赋值到接口指针,此时会完成一个隐式转换。引用计数不变。

4.函数结束。智能指针被析构。引用计数递减。

由以上步骤可以看出,若这个函数执行失败,那么没有任何问题。引用计数的增减是平衡的。但若这个函数成功呢?你可能要回到条款1,去看一看令人头疼的引用计数规则了:

“在返回之前调用AddRef。对于那些返回接口指针的函数,在返回前应用相应的指针调用AddRef。这些函数包括QueryInterfaceCreateInstance。这样当客户从这种函数得到一个接口后,他将无需调用AddRef

很显然这里没有这样做。导致的后果是外界可能有一个指针或者引用接收到了此接口指针,而引用计数却没有被增加!

你可能又想到了在其出栈前手动调整计数的方法。请别这样做,那会使得语义不明确。最佳解决的办法是使用detach操作:

HRESULT hr GetView(int nIndex, IView** ppView)
{
    CComPtr<IView> spView = m_Views[nIndex];
    if (FAILED( spView->IsVisable()))
        return E_FAILD;
    *ppView = spView.Detach();   //在智能指针出栈前,将其与接口指针分离。
    return S_OK;    
}

这或许是智能指针最让人苦恼的地方,他只是从一定程度上实现了引用计数的自动化。而却没有完完全全的解决这个问题。问题的根本在于,智能指针想实现引用计数的自动化,同时又需要兼容COM引用技术遗留下来的三条规则。对于这些潜在的问题,程序员或许只能小心翼翼了。

`attach()` 和 `detach()` 都是 Laravel 中用于建立多对多关联关系的方法。 `attach()` 方法用于将一个或多个新的关联关系添加到中间表中。它接受一个参数,这个参数可以是一个 ID 或 ID 数组。如果你传递的是单个 ID,那么这个 ID 对应的模型就会被添加到中间表中。如果你传递的是一个数组,那么数组中的每个 ID 对应的模型都会被添加到中间表中。 `detach()` 方法用于从中间表中删除一个或多个关联关系。它接受一个参数,这个参数可以是一个 ID 或 ID 数组。如果你传递的是单个 ID,那么这个 ID 对应的模型就会被从中间表中删除。如果你传递的是一个数组,那么数组中的每个 ID 对应的模型都会被从中间表中删除。 下面是一个例子,假设我们有一个 `User` 模型和一个 `Role` 模型,它们之间是多对多的关系,我们可以使用 `attach()` 方法将一个新的关联关系添加到中间表中,使用 `detach()` 方法从中间表中删除一个关联关系: ```php // 获取一个用户 $user = User::find(1); // 使用 attach() 方法添加一个新的角色 $user->roles()->attach(1); // 使用 detach() 方法删除一个现有的角色 $user->roles()->detach(2); ``` 在上面的例子中,我们首先获取了一个 ID 为 1 的用户,然后使用 `attach()` 方法将一个新的角色(ID 为 1)添加到这个用户的角色关联中。接着,我们使用 `detach()` 方法将用户的角色关联中 ID 为 2 的角色从中间表中删除。 希望这个例子能够帮助你理解 `attach()` 和 `detach()` 方法的使用。如果你还有其他问题,请随问我。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值