有时我们必须将不同的对象放在一个容器的同一层次结构中。我读过一些文章说有一些技巧和陷阱。然而,我对这个问题没有大局观。事实上,这种情况在现实世界中经常发生。
例如,一个停车场必须容纳不同类型的汽车;动物园必须容纳不同类型的动物;书店必须包含不同类型的书籍。
我记得有一篇文章说以下两种都不是好的设计,但我忘了它在哪里。
vector<vehicle> parking_lot;
vector<*vehicle> parking_lot;
有人可以为此类问题提供一些基本规则吗?
Some times we have to put different objects in the same hierarchy in one container. I read some article saying there are some tricks and traps. However, I have no big picture about this question. Actually, this happens a lot in the real word.
For example, a parking lot has to contain different types of cars; a zoo has to contain different types of animals; a book store has to contain different types of books.
I remember that one article saying neither of the following is a good design, but I forgot where it is.
vector<vehicle> parking_lot;
vector<*vehicle> parking_lot;
Can anybody offer some basic rules for this kind of question?
发布评论
评论(5)
我在回复类似问题时学到了很多同一个作者,所以我忍不住在这里做同样的事情。简而言之,我编写了一个基准来比较以下在标准容器中存储异构元素问题的方法:
为每种类型的元素创建一个类,并让它们全部继承自公共基类并存储多态基类std::vector 中的指针>。这可能是更通用和灵活的解决方案:
与 (1) 相同,但将多态指针存储在
boost::ptr_vector
中。这不太通用,因为元素完全由向量拥有,但在大多数情况下应该足够了。boost::ptr_vector
的优点之一是它具有std::vector
的接口(不带 *),因此使用起来更简单。 p>使用可包含所有可能元素的 C 联合,然后使用
std::vector
。这不是很灵活,因为我们需要提前知道所有元素类型(它们被硬编码到联合中),而且众所周知,联合不能与其他 C++ 结构很好地交互(例如,存储的类型不能具有构造函数)。使用可包含所有可能元素的
boost::variant
,然后使用std::vector
。这不像联合那样灵活,但处理它的代码要优雅得多。使用
boost::any
(可以包含任何内容),然后使用std::vector
。这非常灵活,但界面有点笨拙且容易出错。这是完整基准测试程序的代码(由于某种原因无法在键盘上运行)。这是我的表现结果:
我的结论:
vector; >
仅当您需要运行时多态性提供的灵活性并且您需要共享所有权时。否则,您将产生大量开销。如果您需要运行时多态性但不关心共享所有权,请使用
boost::ptr_vector
。它将比对应的shared_ptr
快得多,并且界面将更加友好(存储的元素不会像指针一样呈现)。如果您不需要太多灵活性(即您有一小组不会增长的类型),请使用
boost::variant
。它会很快并且代码会很优雅。如果您需要完全的灵活性(您想要存储任何内容),请使用
boost::any
。不要使用联合体。如果您确实需要速度,那么
boost::variant
也同样快。在我结束之前,我想提一下 std::unique_ptr 的向量将是一个不错的选择变得广泛可用(我认为它已经在 VS2010 中)
I learnt a lot writing my reply to a similar question by the same author, so I couldn't resist to do the same here. In short, I've written a benchmark to compare the following approaches to the problem of storing heterogeneous elements in a standard container:
Make a class for each type of element and have them all inherit from a common base and store polymorphic base pointers in a
std::vector<boost::shared_ptr<Base> >
. This is probably the more general and flexible solution:Same as (1) but store the polymorphic pointers in a
boost::ptr_vector<Base>
. This is a bit less general because the elements are owned exclusively by the vector, but it should suffice most of the times. One advantage ofboost::ptr_vector
is that it has the interface of astd::vector<Base>
(without the *), so its simpler to use.Use a C union that can contain all possible elements and then use a
std::vector<UnionType>
. This is not very flexible as we need to know all element types in advance (they are hard-coded into the union) and also unions are well known for not interacting nicely with other C++ constructs (for example, the stored types can't have constructors).Use a
boost::variant
that can contain all possible elements and then use astd::vector<Variant>
. This is not very flexible like the union but the code to deal with it is much more elegant.Use
boost::any
(which can contain anything) and then astd::vector<boost::any>
. That is very flexible but the interface is a little clumsy and error prone.This is the code of the full benchmark program (doesn't run on codepad for some reason). And here are my performance results:
My conclusions:
Use
vector<shared_ptr<Base> >
only if you need the flexibility provided by runtime polymorphism and if you need shared ownership. Otherwise you'll have significant overhead.Use
boost::ptr_vector<Base>
if you need runtime polymorphism but don't care about shared ownership. It will be significantly faster than theshared_ptr
counterpart and the interface will be more friendly (stored elements not presented like pointers).Use
boost::variant<A, B, C>
if you don't need much flexibility (i.e. you have a small set of types which will not grow). It will be lighting fast and the code will be elegant.Use
boost::any
if you need total flexibility (you want to store anything).Don't use unions. If you really need speed then
boost::variant
is as fast.Before I finish I want to mention that a vector of std::unique_ptr will be a good option when it becomes widely available (I think it's already in VS2010)
vector
的问题是该对象仅包含车辆。vector
的问题是您需要适当地分配指针,更重要的是,释放指针。这可能是可以接受的,具体取决于您的项目等...
但是,人们通常在向量中使用某种 smart-ptr (
vector>
或 Qt-处理释放的东西,或者你自己的东西),但仍然允许在同一个容器中存储不同类型的对象。更新
有些人在其他答案/评论中也提到了
boost::ptr_vector
。这也可以很好地用作 ptr 的容器,并通过拥有所有包含的元素来解决内存释放问题。我更喜欢vector>
因为这样我就可以将对象存储在各处,并使用容器移入和移出它们,而不会出现任何问题。我发现这是一种更通用的使用模型,对我和其他人来说更容易掌握,并且可以更好地应用于更大的问题集。The problem with
vector<vehicle>
is that the object only holds vehicles. The problem withvector<vehicle*>
is that you need to allocate and, more importantly, free the pointers appropriately.This might be acceptable, depending on your project, etc...
However, one usually uses some kind of smart-ptr in the vector (
vector<boost::shared_ptr<vehicle>>
or Qt-something, or one of your own) that handles deallocation, but still permits storing different types objects in the same container.Update
Some people have, in other answers/comments, also mentioned
boost::ptr_vector
. That works well as a container-of-ptr's too, and solves the memory deallocation problem by owning all the contained elements. I prefervector<shared_ptr<T>>
as I can then store objects all over the place, and move them using in and out of containers w/o issues. It's a more generic usage model that I've found is easier for me and others to grasp, and applies better to a larger set of problems.问题是:
解决方案是:
一旦您了解了上述几点的“原因和方法”,请查看 boost ptr_containers——感谢手册的提示。
The problems are:
The solution is :
Once you understand the "whys and hows" of the points above look at the boost ptr_containers -- thanks to Manual for the tip.
假设
vehicle
是一个基类,它具有某些属性,然后,从它继承,你可以说有car
和truck
。然后你可以这样做:这将是完全有效的,事实上有时非常有用。这种类型的对象处理的唯一要求是对象的合理层次结构。
其他可以像这样使用的流行对象类型是例如
people
:) 您几乎在每本编程书中都能看到这一点。编辑:
当然,该向量可以用
boost::shared_ptr
或std::tr1::shared_ptr
来包装,而不是原始指针,以便于内存管理。事实上,这是我建议尽一切可能去做的事情。编辑2:
我删除了一个不太相关的示例,这是一个新示例:
假设您要实现某种 AV 扫描功能,并且您有多个扫描引擎。因此,您实现某种引擎管理类,例如
scan_manager
,它可以调用这些引擎的bool scan(...)
函数。然后创建一个引擎接口,例如engine
。它将有一个virtual bool scan(...) = 0;
然后你制作一些引擎
,例如my_super_engine
和my_other_uber_engine
,两者都继承自engine
并实现scan(...)
。然后,您的引擎管理器会在初始化期间的某个地方用my_super_engine
和my_other_uber_engine
的实例填充std::vector
并通过以下方式使用它们按顺序或根据您想要执行的任何类型的扫描对它们调用bool scan(...)
。显然,这些引擎在scan(...)
中执行的操作仍然未知,唯一有趣的一点是bool
,因此管理器可以以相同的方式使用它们,而无需任何操作修改。同样的情况也适用于各种游戏单位,例如
scary_enemies
以及orks
、drunks
和其他令人不快的生物。它们都实现了void Attack_good_guys(...)
并且您的evil_master
会创建其中的许多并调用该方法。这确实是一种常见的做法,只要所有这些类型实际上都是相关的,我就不会称之为糟糕的设计。
Say
vehicle
is a base class, that has certain properties, then, inheriting from it you have say acar
, and atruck
. Then you can just do something like:This would be perfectly valid, and in fact very useful sometimes. The only requirement for this type of object handling is sane hierarchy of objects.
Other popular type of objects that can be used like that are e.g.
people
:) you see that in almost every programming book.EDIT:
Of course that vector can be packed with
boost::shared_ptr
orstd::tr1::shared_ptr
instead of raw pointers for ease of memory management. And in fact that's something I would recommend to do by all means possible.EDIT2:
I removed a not very relevant example, here's a new one:
Say you are to implement some kind of AV scanning functionality, and you have multiple scanning engines. So you implement some kind of engine management class, say
scan_manager
which can callbool scan(...)
function of those. Then you make an engine interface, sayengine
. It would have avirtual bool scan(...) = 0;
Then you make a fewengine
s likemy_super_engine
andmy_other_uber_engine
, which both inherit fromengine
and implementscan(...)
. Then your engine manager would somewhere during initialization fill thatstd::vector<engine *>
with instances ofmy_super_engine
andmy_other_uber_engine
and use them by callingbool scan(...)
on them either sequentially, or based on whatever types of scanning you'd like to perform. Obviously what those engines do inscan(...)
remains unknown, the only interesting bit is thatbool
, so the manager can use them all in the same way without any modification.Same can be applied to various game units, like
scary_enemies
and those would beorks
,drunks
and other unpleasant creatures. They all implementvoid attack_good_guys(...)
and yourevil_master
would make many of them and call that method.This is indeed a common practice, and I would hardly call it bad design for as long as all those types actually are related.
你可以参考这个Stroustrup对问题的回答为什么我不能分配一个向量<苹果*>到一个向量<水果*>?。
You can refer to this Stroustrup's answer to the question Why can't I assign a vector< Apple*> to a vector< Fruit*>?.