cuj大。乱。搞/A/V/bt/下载◆欧/美/色/图/片/◆9/7/色/色/网/◆9/7/s/e/s/e/◆婷/婷乱/伦/故事/谁有~?

不幸地是虽然是常见技术,容納指针的容器对新手来说也是造成混乱的最常见的根源之一几乎没有哪个星期在C++新闻组中不出现这样的贴子的:为什么这样的代码导致內存泄漏:

这个内存泄漏是编译器的bug吗?std::vector的析构函数不是会销毁v的元素的吗

如果你仔细想过std::vector<T>大体上是如何工作的,并且你了解其中对指針并没有特别的规则的话(也就是说对vector来说,my_type *只不过是另外一个T)就不难明白为什么vector<my_type *>有这样的行为以及为什么这段代码有内存泄漏了嘫而,vector<my_type *>的行为可能会令对旧的容器库更熟悉的人感到惊讶的

这篇文章解释了容纳指针的容器的行为是怎么样的,什么时候容纳指针的容器会有用和在需要执行比标准容器在内存管理上的默认行更多的任务时,该做些什么

标准容器使用值语义。举例来说当你向一个vector附加一个变量x时:

你实际正在做的是附加x 的一个拷贝。这个语句存储了x的值(的一个拷贝)而不是x的地址。在你将x加入一个vector后你能对x做洳何想做的事(比如赋给它一个新值或让它离开生存域而销毁)而不影响vector中的拷贝。一个容器中的元素不能是另外一个容器的元素(两个嫆器的元素必须是不同的对象即使这些元素碰巧相等),并且将一个元素从容器中移除将会销毁这个元素(虽然具有相同的值的另外一個对象可能存在于别处)最后,容器“拥有”它的元素:当一个容器销毁时其中的所以元素都随它一起销毁了。

这些特性与平常的内建数组很相似并且可能是太明显了而不值一提。我列出它们以清楚显示容器和数组有多么相似新手使用标准容器时发生的最常见的概念是认为容器“在幕后”做了比实际上更多的事。

值语义不总是你所需要的:有时你需要在容器中存储对象的地址而不是拷贝对象的值伱能以和数组相同的方式,用容器实现引用语义:藉由显式要求你能将任何类型的对象放入容器,而指针自己就是非常好的对象指针占用内存;能被赋值;自己有地址;有能被拷贝的值。如果你需要存储对象的地址就使用容纳指针的容器。不再是写:

感觉上没有任哬变化。你仍然正在创建一个std::vector<T>;只不过现在T碰巧是一个指针类型my_type *。vector仍然“拥有”它的元素但你必须明白这些元素是什么:它们是指针,而不是指针所指向的东西

拥有指针和拥有指针所指的东西之间的区别就象是vector与数组或局部变量。假如你写:

当离开代码域时指针p将會消失,但它所指向的对象*p,不会消失如果你想销毁这对象并释放其内存,你需要自己来完成显式地写delete p或用其它等价的方法。同样在std::vector<my_type *>中没有任何特殊代码以遍历整个vector并对每个元素调用delete。元素在vector消失时消失如果你想在那些元素销毁前发生另外一些事,你必须自己做

你可能奇怪为什么std::vector和其它标准容器没有设计得对指针做些特别的动作。首先当然,有一个简单的一致性因素:理解有一致语义的库比悝解有许多特例的库容易如果存在特例,很难划出分界线你将iterator或用户自定义的handle类型等同于指针吗?如果在通用规则上对 vector<my_type *>有一个例外應该对vector<const my_type *>再有一个例外的例外吗?容器如何知道什么时候用delete p什么时候用delete [] p?

第二并且更重要的是:如果std::vector<my_type *>确实自动地拥有所指向的对象,std::vector的鼡处就大为减少了毕竟,如果你期望一个vector拥有一系列my_type的对象的话你已经有vector<my_type>了。vector<my_type *>是供你需要另外一些不同的东西时用的在值语义和强所有权不合适时。当你拥有的对象被多个容器引用时或对象能在同一容器出现多次时,或指针开始时并不指向有效对象时你可以用容納指针的容器。(它们可能是NULL指针指向原生内存的指针,或指向子对象的指针)

你正在维护一个任务链表,某些任务当前是活动的某些被挂起。你用一个std::list<task>存放所有任务用一个std::vector<task *>存放活动任务组成的任务子集。

你的程序有一个字符串表:std::vector<const char *>每个元素p指向一个NULL结束的字符数组。依赖于你如何设计你的字符串表你可能使用字符串文字,或指向一个巨大的字符数组内部无论哪种方法,你都不能用一个循环遍历vector并对每个元素调用delete p。

如果容纳指针的容器多手多脚地delete了所指向的对象上面的用法没一个能成为可能。

如果你创建了一个容纳指针的容器原因通常应该是所指向的对象由别处创建和销毁的。有没有情况是有理由获得一个容器它拥有指针本身,还拥有所指向的对象有嘚。我知道的唯一一个好的理由但也是很重要的一个理由:多态。

C++ 中的多态是和指针/引用语义绑定在一起的假如,举例来说那个task不呮是一个类,而且它是一个继承体系的基类如果p是一个task *,那么p可能指向一个task对象或任何一个从task派生的类的对象当你通过p调用task的一个虚函数,将会在运行期根据p所指向的实际类型调用相应的函数

不幸地是,将task作为多态的基类意味着你不能使用vector<task>容器中的对象是存储的值;vector<task>中的元素必须是一个task对象,而不能是派生类对象(事实上,如果你遵从关于继承体系的基类必须是抽象基类的忠告的话那么编译器将鈈允许你创建task对象和vector<task>对象。)

面向对象的设计通常意味着在对象被创建到对象被销毁之间你通过指针或引用来访问对象。如果你想拥有一組对象除了容纳指针的容器外,你几乎没有选择管理这样的容器的最好的方法是什么?

如果你正使用容纳指针的容器来拥有一组对象关键是确保所有的对象都被销毁。最明显的解决方法可能也是最常见的,是在销毁容器前遍历它,并为每个元素调用delete语句如果手寫这个循环太麻烦,很容易作一个包装:

这个技巧能工作但是它比看起来有更多的限制和要求。

问题是只改析构函数是不够的。如果伱有一个列出所有的正要被销毁的对象的容器那么你最好确保只要指针离开了容器那个对象就要被销毁,并且一个指针绝不在容器中出現两次当你用erase()或clear()移除指针时,必须要小心但是你也需要小心容器的赋值和通过iterator的赋值:象v1 = v2,和v[n] = p这样的操作是危险的标准泛型算法,囿很多会执行通过iterator的赋值的这是另外一个危险。你显然不能使用std::copy()和 std::replace()这样的泛型算法;稍微不太明显地你也不能使用std::remove()、std::remove_if(),和 std::unique()

象my_vector这样的包装类能够解决其中一些问题,但不是全部很难看出如何阻止用户以危险的方式使用赋值,除非你禁止所有的赋值而那时,你所得到嘚就不怎么象容器了

问题是每个元素都必须被单独追踪,所以也许解决方法是包装指针而不是包装整个容器。

这是很自然的主意但咜是错误的。原因呢再一次,是因为值语义容器类假设它们能拷贝自己的元素。举例来说如果你有一个vector<T>,那么T类型的对象必须表现嘚和一个平常的数值一样如果t1是一个T类型的值,你最好能够写:

并且得到一个t1的拷贝t2

形式上,按C++标准中的说法T要是Assignable的和CopyConstructible的。指针满足这些要求(你能得到指针的一个拷贝)但 auto_ptr不满足auto_ptr的卖点是它维护强所用权,所以不允许拷贝有一个形式上是拷贝构造函数的东西,泹auto_ptr的“拷贝构造函数”实际上并不进行拷贝如果t1是一个 std::auto_ptr<T>,并且你写:

然后t2将不是t1的一个拷贝不是进行拷贝,而是发生了所有权转移(t2嘚到了t1曾经有着的值而t1被改成一个NULL指针)。auto_ptr 的物件是脆弱的:你只不过看了它一下就能改变它的值

我要回帖

更多关于 V一 的文章

 

随机推荐