将对象存储在矢量中:最好存储对象或指向它的指针

Storing objects in vector: better to store the object or a pointer to it?

本文关键字:对象 存储 指针      更新时间:2023-10-16

我有一些对象在整个应用程序生命周期中都在使用:它们从未被销毁。我将它们存储在矢量中:

declaration:
    std::vector<DoubleValue> bidDealIndexes;
construction:
    bidDealIndexes(Instrument::InstrumentsCount())
 usage:
    DoubleValue& v = bidDealIndexes[0];

或者,我可以存储指针:

declaration:
    std::vector<DoubleValue*> bidDealIndexes;
construction:
    for (int i = 0; i < Instrument::InstrumentsCount(); i++) {
        bidDealIndexes.push_back(new DoubleValue());
    }
 usage:
    DoubleValue* v = bidDealIndexes[0];

问题是哪一个更可取?应该避免这两种选择中的一种(或两种)吗?

对象总是首选对象,除非需要,否则应避免使用指针。主要的论点是,一个人不应该毫无理由地使事情复杂化,指针会导致复杂化。在您的示例中,不需要指针。

按值存储

在可能的情况下,将对象直接存储到向量(或其他STL容器)中最好留给基元类型。这是因为每次插入实际上都会创建存储对象的副本。如果存储类对象,例如DoubleValue类型,则每次push_back时都会调用复制构造函数。如果复制构造函数很便宜(即类内的存储空间很小,复制构造函数几乎不工作),那么按值存储将使您的生活变得简单。对于更复杂的对象,甚至可能无法按值存储。例如,无法复制iostream,因此无法将它们放置到向量中。对于具有昂贵的复制构造函数的类,按值存储将减慢执行速度并增加内存占用。

摘要:用于轻量级对象

原始指针存储

随着C++11(以及其他一些第三方库,如boost)的出现,由于跟踪所有权和对象寿命的复杂性,不鼓励通过原始指针进行存储。这是Stack Overflow中充分介绍的另一个主题领域。如果您不能使用C++11,那么在使用原始指针存储时要小心。这可能比按值存储更具性能,并允许存储无法复制的对象类型(如iostream)。

摘要:当智能指针不可用时用于重量级对象

智能指针存储

C++11引入了std::shared_ptr及其同类形式的智能指针。它们通过引用计数来管理对象的生存期,并遵循RAII(资源获取即初始化)模式。净效果是,从你的角度来看,它们就像原始指针一样工作,但你不必担心:

  1. 指向已删除对象的指针
  2. 谁负责删除对象

我不知道shared_ptr是否最适合您的情况,但智能指针的使用看起来像:

std::vector<std::shared_ptr<DoubleValue> > bigDealIndexes;
bigDealIndexes.push_back(std::shared_ptr<DoubleValue>(new DoubleValue));
std::cout << "Value of first big deal: " << *(bigDealIndexes[0]) << std::endl;
std::cout << "Member 'a':              " << bigDealIndexes[0]->a << std::endl;

摘要:使用C++11、boost或类似库时用于重量级对象

在实践中,如果您的对象是POD,并且向量的长度小于几千个项目,则使用将获得更好的性能

std::vector<DoubleValue>

与缓存未命中相比,副本非常便宜。

如果DoubleValue(非常)大或不可压缩,并且矢量也是对象寿命的控制器,则使用

std::vector<std::unique_ptr<DoubleValue>>

如果DoubleValue很大,并且您更喜欢按值样式(您应该这样做),那么您可以考虑使用pimpl习惯用法实现DoubleValue,并使其可复制或可移动,这将允许您将其存储在std::vector中。

如果向量是恒定长度,请考虑使用std::array
否则,请坚持元素的std::vector。如果必须插入一整批元素,则可能需要在存储元素之前调用reserve(),以避免多余的复制。

仅供专家使用: 如果对象的移动成本很高,并且管理添加的成员的生存期是其他人的问题(例如,它们在程序结束前一直有效),那么您可以考虑将存储指针作为优化。不过,只有在仔细衡量和考虑后才能考虑。

在c++11中,按值存储肯定是首选。在c++03中,我可能仍然更喜欢按值存储,除非性能测量结果显示这是一个问题。

在c++11中,您正在使用的vector<T>(N)构造函数已更改为使用值初始化,而不是制作N副本。

按值存储更简单,在您的情况下可能会表现得更好。如果性能是一个问题,请测量并查看。

编辑:我做了一些粗略的测量,通过值、原始指针和;unique_ptr和by value位居榜首。