165

从头开始设计一个新系统。我将使用 STL 来存储某些长寿命对象的列表和映射。

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

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

玩指针的两个非常明显的缺点: 1) 我必须自己在 STL 之外的范围内管理这些对象的分配/释放。2) 我无法在堆栈上创建临时对象并将其添加到我的容器中。

还有什么我想念的吗?

4

10 回答 10

68

由于人们正在关注使用指针的效率。

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

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

于 2008-09-26T19:18:20.930 回答
47

这真的取决于你的情况。

如果您的对象很小,并且复制对象是轻量级的,那么在我看来,将数据存储在 stl 容器中很简单且更易于管理,因为您不必担心生命周期管理。

如果您的对象很大,并且具有默认构造函数没有意义,或者对象的副本很昂贵,那么使用指针存储可能是要走的路。

如果您决定使用指向对象的指针,请查看Boost Pointer Container Library。这个 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 算法一起工作,这非常方便。

还有一些工具可以将容器中指针的所有权转移给调用者(通过大多数容器中的释放函数)。

于 2008-09-26T19:41:30.203 回答
39

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

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

于 2008-09-26T19:13:25.340 回答
22

很抱歉在事件发生 3 年后加入,但这里有一个警告......

在我上一个大项目中,我的中心数据结构是一组相当简单的对象。项目开始大约一年后,随着需求的发展,我意识到对象实际上需要是多态的。花了几个星期的艰难和讨厌的脑部手术,将数据结构修复为一组基类指针,并处理对象存储、铸造等方面的所有附带损害。我花了几个月的时间来说服自己新代码是有效的。顺便说一句,这让我认真思考 C++ 的对象模型设计得多么好。

在我目前的大项目中,我的中心数据结构是一组相当简单的对象。项目进行了大约一年(恰好是今天),我意识到对象实际上需要是多态的。回到网上,找到了这个线程,找到了 Nick 的 Boost 指针容器库的链接。这正是我上次为了解决所有问题而必须写的,所以这次我会试一试。

无论如何,对我来说,道德是:如果你的规范不是 100% 确定的,那就去寻求指导,你以后可能会为自己节省很多工作。

于 2012-01-26T11:01:39.193 回答
19

为什么不两全其美:做一个智能指针容器(例如boost::shared_ptror std::shared_ptr)。您不必管理内存,也不必处理大型复制操作。

于 2008-09-26T19:15:17.583 回答
11

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

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

于 2008-09-26T19:16:22.853 回答
3

您似乎很好地掌握了差异。如果对象很小且易于复制,那么一定要存储它们。

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

@ Torbjörn对切片提出了一个很好的观点。

于 2008-09-26T19:14:10.220 回答
3

使用指针会更有效,因为容器只会复制指针而不是完整的对象。

这里有一些关于 STL 容器和智能指针的有用信息:

为什么将 std::auto_ptr<> 与标准容器一起使用是错误的?

于 2008-09-26T19:14:44.620 回答
2

如果要在代码中的其他地方引用对象,请存储在 boost::shared_ptr 的向量中。这样可以确保在调整向量大小时指向对象的指针仍然有效。

IE:

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
于 2008-09-27T05:54:38.090 回答
1

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

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

这篇文章中最重要的一点是自己测试它,使用你的对象。

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

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

我使用 std::list 来保存这两种类型的对象,我发现将对象存储在列表中比使用指针稍微快一些。另一方面,性能非常接近,因此取决于它们在您的应用程序中的使用方式。

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

于 2013-07-19T01:33:36.953 回答