条款33:提防在指针的容器上使用类似remove的算法

优质
小牛编辑
130浏览
2023-12-01

条款33:提防在指针的容器上使用类似remove的算法

你在管理一堆动态分配的Widgets,每一个都可能通过检验,你把结果指针保存在一个vector中:

class Widget{
public:
	...
	bool isCertified() const;	// 这个Widget是否通过检验
	...
};

vector<Widget*> v;			// 建立一个vector然后用
...				// 动态分配的Widget
v.push_back(new Widget);		// 的指针填充

当和v工作一段时间后,你决定除去未通过检验的Widget,因为你不再需要它们了。记住条款43的警告尽量用算法调用代替显式循环和读过的条款32关于remove和erase之间关系的描述,你自然会想到转向erase-remove惯用法,虽然这次你使用了remove_if:

v.erase(remove_if(v.begin(), v.end(),				// 删除未通过检验的
			not1(mem_fun(&Widget::isCertified))),	// Widget指针
				v.end());				// 关于mem_fun的信息
								// 参见条款41

突然你开始担心erase的调用,因为你朦胧的记起条款7关于摧毁容器中的一个指针也不会删除指针指向的东西的讨论。这是个合理的担心,但在这里,太晚了。当调用erase时,极可能你已经泄漏了资源。担心erase,是的,但首先,担心一下remove_if。

我们假设在调用remove_if前,v看起来像这样,我已经指出了未通过检验的Widget:

图33-1

在调用remove_if后,一般来说v看起来像这样(包含从remove_if返回的迭代器):

图33-2

如果你看不懂这个转换,请转向条款32,因为它准确地解释了调用remove——或者,在这个例子里,remove_if——做了什么。

资源泄漏的理由现在很明朗了。指向Widget B和C的“删除的”指针被vector中后面的“不删除的”指针覆盖。没有什么指向两个未通过检验的Widget,它们也没有被删除,它们的内存和其他资源泄漏了。

一旦remove_if和erase返回后,情况看起来像这样:

图33-3

这造成资源泄漏尤其明显了,现在你也很清楚为什么应该努力避免在动态分配的指针的容器上使用remove和类似算法(也就是,remove_if和unique)。在很多情况下,你会发现partition算法(参见条款31)是合理的替代品。

如果你无法避免在那样的容器上使用remove,排除这个问题一种方法是在应用erase-remove惯用法之前先删除指针并设置它们为空,然后除去容器中的所有空指针:

void delAndNullifyUncertified(Widget*& pWidget)		// 如果*pWidget是一个
{							// 未通过检验Widget,
	if (!pWidget->isCertified()) {			// 删除指针
		delete pWidget;				// 并且设置它为空
		pWidget = 0;
	}
}

for_each(v.begin(), v.end(),			// 把所有指向未通过检验Widget的
			delAndNullifyUncertified);	// 指针删除并且设置为空

v.erase(remove(v.begin(), v.end(),			// 从v中除去空指针
			static_cast<Widget*>(0)),	// 0必须映射到一个指针,
			v.end());			// 让C++可以
						// 正确地推出remove的
						// 第三个参数的类型

当然,这假设vector并不容纳任何你想保留的空指针。如果有的话,你可能必须自己写循环来按你的方式删除指针。在你遍历容器时从容器中删除元素有一些细微的要注意的地方,在考虑那种方法之前确定已经读过条款9。

如果你把指针的容器替换成执行引用计数的智能指针的容器,删除相关的困难就不存在了,你可以直接使用erase-remove惯用法:

template<typename T>					// RCSP = “引用计数
class RCSP { ...};						// 智能指针”
typedef RCSP< Widget> RCSPW;				// RCSPW = “RCSP to Widget”
vector<RCSPW > v;						// 建立一个vector,用动态
...							// 分配Widget的
v.push_back(RCSPW(new Widget));				// 智能指针填充它
...
v.erase(remove_if(v.begin(), v.end(),			// erase未通过检验的
		not1 (mem_fun(&Widget::isCertified))),	// Widget的指针
			v.end());				// 没有资源泄漏

要让这些工作,你的智能指针类型就必须可以(比如RCSP<Widget>)隐式转换为相应的内建指针类型(比如Widget*)。那是因为容器持有智能指针,但被调用的成员函数(比如Widget::isCertified)要的是内建指针。如果不存在隐式转换,你的编译器会抗议的。

如果在你的程序工具箱中碰巧没有一个引用计数智能指针模板,你应该从Boost库中得到shared_ptr模板。关于Boost的介绍,请看条款50。

不管你怎么选择处理动态分配指针的容器,通过引用计数智能指针、在调用类似remove的算法前手动删除和废弃指针或者一些你自己发明的技术,本条款的指导意义依然一样:提防在指针的容器上使用类似remove的算法。没有注意这个建议的人只能造成资源泄漏。