在STL容器中保存对象指针而不是对象本身更好吗?
这个问题的出现是因为在使用STL容器时,我们需要考虑是将对象本身存储在容器中,还是将对象的指针存储在容器中。这两种方法各有利弊,因此需要根据具体情况进行选择。
将指针存储在容器中可以提供更大的灵活性,因为我们不需要使用对象的操作符,或者可以使用具有副作用和/或高代价的操作符。容器本身会在最坏的情况下复制指针,而这种复制的代价是相当低的。此外,可以在同一继承层次结构中存储不同大小的对象(特别是如果它们具有虚方法)。
另一方面,将对象本身存储在容器中可以降低访问开销,因为您不需要每次访问元素时解引用指针。此外,它还将改善数据局部性(从而降低缓存未命中、页面未命中等),减少内存消耗和碎片化。
没有普遍适用的规则。对于经常读取而很少修改的容器,可能更适合直接将没有副作用的小对象存储在其中,而对于经常修改和调整大小的容器,可能更适合将具有昂贵副作用的构造函数和复制操作符的大对象存储在其他地方。
您必须考虑您的使用情况,并权衡每种方法的利弊。
在C++中,与许多其他现代语言不同,C++更适用于值类型。如果存储指针,就必须管理所得对象的生命周期。除非使用unique_ptr
或类似的智能指针,否则语言无法帮助您管理。追踪对象丢失会导致内存泄漏,而在处理完对象后仍保留对它们的追踪会导致悬空引用/指针。这两种情况都是非常常见的错误。
如果存储值(或类似值类型),并且教会您的数据如何高效地移动自己,那么vector
将在内存中连续存储它们。
在现代计算机上,CPU速度很快,而内存的速度非常慢。一台典型的计算机将拥有3级缓存,以尝试加快内存访问速度,但如果您的数据分散在内存中(因为使用了堆来存储对象),CPU几乎无法确定您将要访问的位置。
如果您的数据在一个连续的缓冲区中,不仅可以一次从内存中获取多个对象,而且CPU还能够猜测您将要访问的下一个内存块,并为您预取它。
所以简而言之,如果您的对象大小适中,请使用实际对象的vector
副本。如果它们稍大一些,请将频繁访问的内容放在对象中,将不太频繁访问的大部分放在对象内的vector
中,并编写高效的move
语义。然后将对象本身存储在vector
中。
但是也有一些例外情况。
首先,值类型的多态性很难处理,因此您最终会经常使用堆。
其次,某些对象的位置是其标识的一部分。向量会移动对象的位置,重新安置对象的成本可能不值得麻烦。
第三,性能通常并不重要。所以你做简单的事情。同时,值类型并不那么困难,尽管过早优化是一个坏主意,但过早地取消优化也是一个坏主意。学习如何处理值类型和连续向量是重要的。
最后,要学习零规则。零规则是指管理资源的对象应仔细编写其复制/移动/赋值/移动赋值/析构函数,以遵循值语义规则(或阻止)。然后使用这些资源管理对象的对象通常不需要真正编写它们的移动/复制/赋值/移动赋值/析构函数-它们可以留空、使用=default
或类似的方式。
您不编写的代码往往比您编写的代码更少出错。
问题的出现的原因是讲义中对于使用STL容器中的对象指针和对象本身的选择存在一些错误。讲义中提到,使用vector会使用拷贝构造函数和拷贝赋值运算符,但是如果编译器提供的默认实现对你来说已经足够好用的话,你不需要自己提供定义。
然而,是否定义拷贝构造函数、赋值运算符和析构函数,与将对象放入容器中的决策是独立的。只有当你的类手动分配资源时,才需要定义这三个函数。否则,使用默认实现就可以了。
回到主要问题,选择存储指针还是对象本身主要取决于你的集合的语义,以及是否需要存储具有多态行为的对象。
如果不需要多态行为,并且创建对象的拷贝相对廉价,使用vector是一个更好的选择,因为它可以让容器为你管理资源。
如果拷贝操作很昂贵,并且你需要多态行为,那么你需要使用指针。然而,这些指针不一定需要是裸指针:C++标准库提供了智能指针,它们会自动处理资源的清理,因此你不需要手动销毁对象。