我应该将整个对象还是指向对象的指针存储在容器中?


162

从头开始设计新系统。我将使用STL来存储某些长期存在的对象的列表和地图。

问:我应该确保我的对象具有副本构造函数并将对象的副本存储在我的STL容器中,还是通常更好地自己管理生命和作用域并仅将指向这些对象的指针存储在我的STL容器中?

我意识到这在细节上有些欠缺,但是我正在寻找“理论上”更好的答案(如果存在),因为我知道这两种解决方案都是可行的。

使用指针有两个非常明显的缺点:1)我必须在超出STL的范围内自己管理这些对象的分配/取消分配。2)我不能在堆栈上创建一个临时对象并将其添加到我的容器中。

还有什么我想念的吗?


36
上帝,我爱这个网站,这是我今天想到的
确切

2
另一个有趣的事情是,我们应该检查指针是否实际上已添加到集合中,如果不是,则应该调用delete以避免内存泄漏... if((set.insert(pointer))。second = false) {删除指针;}
javapowered

Answers:


68

由于人们正在使用指针来提高效率。

如果您正在考虑使用std :: vector,并且更新很少,并且您经常对集合进行迭代,并且它是一种非多态类型,则存储对象“副本”会更有效,因为您可以获得更好的引用位置。

Otoh,如果更新是常见的,存储指针将节省复制/重定位成本。


7
在缓存局部性方面,如果将指针与指向对象的自定义分配器一起使用,则将指针存储到向量中将非常有效。自定义分配器必须照顾缓存的位置,例如使用new放置(请参见en.wikipedia.org/wiki/Placement_syntax#Custom_allocators)。
艾米特

47

这真的取决于您的情况。

如果您的对象很小,并且做对象的副本很轻便,那么在我看来,将数据存储在stl容器中非常简单,而且更易于管理,因为您不必担心生命周期管理。

如果您的对象很大,并且没有默认的构造函数,或者对象的副本很昂贵,那么使用指针存储可能是解决之道。

如果决定使用指向对象的指针,请查看Boost指针容器库。这个boost库包装了所有STL容器,以便与动态分配的对象一起使用。

每个指针容器(例如ptr_vector)在将对象添加到容器时都拥有该对象的所有权,并为您管理这些对象的生存期。您还可以通过引用访问ptr_容器中的所有元素。这使您可以执行以下操作

class BigExpensive { ... }

// create a pointer vector
ptr_vector<BigExpensive> bigVector;
bigVector.push_back( new BigExpensive( "Lexus", 57700 ) );
bigVector.push_back( new BigExpensive( "House", 15000000 );

// get a reference to the first element
MyClass& expensiveItem = bigList[0];
expensiveItem.sell();

这些类包装了STL容器并可以使用所有STL算法,这确实非常方便。

还有一些用于将容器中指针的所有权转移到调用者的功能(通过大多数容器中的释放功能)。


38

如果要存储多态对象,则始终需要使用基类指针的集合。

也就是说,如果您打算在集合中存储不同的派生类型,则必须存储指针或被切片守护进程吞噬。


1
我爱切片恶魔!
idichekop

22

很抱歉在活动结束后的3年内跳转,但请注意此处...

在上一个大型项目中,我的中央数据结构是一组相当简单的对象。随着需求的发展,进入项目大约一年后,我意识到该对象实际上需要是多态的。经过数周的艰苦而艰苦的脑部手术,将数据结构固定为一组基类指针,并处理了对象存储,投射等中的所有附带损害。我花了几个月的时间才能使自己确信新代码正在运行。顺便说一句,这使我很难思考C ++对象模型的设计得如何。

在我当前的大型项目中,我的中央数据结构是一组相当简单的对象。在进入项目大约一年后(恰好是今天),我意识到该对象实际上需要是多态的。回到网上,找到了该线程,并找到了Nick到Boost指针容器库的链接。这正是我上次要修复所有问题时要写的内容,因此这次我会尝试一下。

无论如何,对我而言,这是道德上的问题:如果您的规格不是100%固定不变,那就去寻找指点,以后可能会节省很多工作。


规格永远不会一成不变。我认为这并不意味着您应该专门使用指针容器,尽管Boost指针容器似乎使该选项更具吸引力。我怀疑如果决定将对象容器转换为指针容器,则需要立即彻底检查整个程序。在某些设计中可能就是这种情况。在这种情况下,这是一个脆弱的设计。在这种情况下,不要将问题归咎于对象容器的“弱点”。
allyourcode

您可能已经将值语义留在向量中,并在其中进行了多态行为。
Billy ONeal

19

为什么不同时兼顾两个方面:做一个智能指针容器(例如boost::shared_ptrstd::shared_ptr)。您不必管理内存,也不必处理大型复制操作。


这种方法与Nick Haddad通过使用Boost Pointer容器库提出的建议有何不同?
ThorstenSchöning16年

10
@ThorstenSchöningstd :: shared_ptr不添加对boost的依赖。
詹姆斯·约翰斯顿

您不能使用共享指针来处理多态性,因此除非您明确地
强制转换

11

通常,将对象直接存储在STL容器中最好,因为它最简单,最有效并且最容易使用对象。

如果您的对象本身具有不可复制的语法或是抽象的基本类型,则需要存储指针(最简单的方法是使用shared_ptr)


4
如果您的对象很大,并且您经常移动元素,那不是最有效的方法。
allyourcode

3

您似乎对差异有很好的了解。如果对象很小并且易于复制,则一定要存储它们。

如果没有,我会考虑将智能指针(不是auto_ptr,即计数智能指针的引用)存储到您在堆上分配的指针。显然,如果选择智能指针,则无法存储临时堆栈分配的对象(如您所说)。

@ Torbjörn很好地介绍了切片。


1
哦,永远 永远 永远创造的auto_ptr的集合
TorbjörnGyllebring

是的,auto_ptr不是一个智能指针-它不引用计数。
Lou Franco

auto_ptr也不具有非破坏性复制语义。分配auto_ptr从one到的动作another将释放引用one并进行更改one
安迪·芬肯斯塔特


2

如果要在代码的其他地方引用这些对象,则将其存储在boost :: shared_ptr的向量中。这样可以确保如果调整矢量的大小,则指向该对象的指针将保持有效。

即:

std::vector<boost::shared_ptr<protocol> > protocols;
...
connection c(protocols[0].get()); // pointer to protocol stays valid even if resized

如果没有其他人存储指向对象的指针,或者列表没有增长和缩小,则仅存储为普通的对象:

std::vector<protocol> protocols;
connection c(protocols[0]); // value-semantics, takes a copy of the protocol

1

这个问题困扰了我一段时间。

我倾向于存储指针,但是我还有一些其他要求(SWIG lua包装器)可能不适用于您。

这篇文章中最重要的一点是使用对象自己进行测试

我今天这样做是为了测试对1000万个对象的集合调用成员函数的速度(500次)。

该函数根据xdir和ydir(所有float成员变量)更新x和y。

我使用std :: list来保存两种类型的对象,并且发现将对象存储在列表中比使用指针要快一些。另一方面,性能非常接近,因此归结为它们将如何在您的应用程序中使用。

作为参考,在我的硬件上使用-O3时,指针花了41秒完成,而原始对象花了30秒完成。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.