将大型对象放入堆中的最佳方式是什么

What is the best way to put large objects on the heap?

本文关键字:最佳 方式 是什么 大型 对象      更新时间:2023-10-16

我正在处理一个项目,该项目需要从数据文件加载许多对象并将它们存储在内存中。由于有人告诉我堆栈空间很少,而且堆上应该有大量数据,所以我把所有数据都放在堆上。然而,我的印象是我做得有点过火了。

我目前的设计是这样的:

class RoadMap
{
    unique_ptr<set<unique_ptr<Node>>> allNodes;
    void addNode(unique_ptr<Node> node)
    {
        this->allNodes->insert(std::move(node));
    }
}
int main()
{
    unique_ptr<RoadMap> map(new RoadMap());
    // open file etc.
    for (auto nodeData : nodesInFile)
    {
        map->addNode(unique_ptr<Node>(new Node(nodeData)));
    }
}

据我现在所知,这会产生很多开销,因为其中涉及到许多我认为不需要的独特指针。如果我理解正确,在"指针链"中只有一个唯一的指针屏障就足够了。然而,我不确定这样做的最佳做法是什么。

选项1

class RoadMap
{
    unique_ptr<set<Node>> allNodes;
    void addNode (Node node)
    {
        this->allNodes->insert(node);
    }
}
int main()
{
    RoadMap map;
    //open file etc.
    for (auto nodeData : nodesInFile)
    {
        map.addNode(Node(nodeData));
    }
}

在我看来,这样做的好处是RoadMap类本身是唯一需要处理堆分配的类,并且在创建set时只需要处理一次。

选项2

class RoadMap
{
    set<Node> allNodes;
    void addNode (Node node)
    {
        this->allNodes.insert(node);
    }
}
int main()
{
    unique_ptr<RoadMap> map(new RoadMap());
    // open file etc.
    for (auto nodeData : nodesInFile)
    {
        map->addNode(Node(nodeData));
    }
}

这里唯一的指针只在主函数中,这意味着RoadMap类的用户需要知道这个对象可能会变得很大,应该放在堆栈中。我不认为这是一个太好的解决方案。

选项3

class RoadMap
{
    set<unique_ptr<Node>> allNodes;
    void addNode(unique_ptr<Node> node)
    {
        this->allNodes.insert(std::move(node));
    {
}
int main()
{
    RoadMap map;
    // open file etc.
    for (auto nodeData : nodesInFile)
    {
        map.addNode(unique_ptr<Node>(new Node(nodeData)));
    }
}

该解决方案使用许多唯一指针,这意味着在删除RoadMap时,需要调用许多析构函数和deletes。此外,RoadMap调用方在添加节点时必须提供unique_ptr,这意味着他必须自己进行堆分配。


现在,我赞成方案1而不是其他方案。然而,我编写C++的时间相对较短,不确定我是否完全理解内存管理背后的概念,这就是为什么我希望你验证我的观点。我认为选项1是最好的方法,这是正确的吗?你有关于这类事情的最佳实践的其他参考资料吗?

Node一个移动构造函数和移动赋值运算符(使对集合的操作变得便宜),然后混合使用选项1和选项2。std::set将已经在堆中分配其内容,因此您不必担心在堆上分配RoadMap。注意addNode内的额外std::move,以允许将Node移动到集合中。

class RoadMap
{
    set<Node> allNodes;
    void addNode (Node node)
    {
        allNodes.emplace(std::move(node));
    }
};
int main()
{
    RoadMap map;
    // open file etc.
    for (const auto& nodeData : nodesInFile)
    {
        map.addNode(Node(nodeData));
    }
}

它们彼此都有很大的不同。

为了简单起见,我建议选择2。但在某些操作(如sort等)中,它可能更需要性能,因为您将移动整个Node,而不是指向它的指针。

我认为这不是问题,因为您使用的是set。您仍然可以通过在Node对象上使用移动语义来优化这一点。没有这个,你仍然使用一个副本,每次添加。

我提到的上述问题可能是vector的问题。直接存储对象的另一个问题是缺乏多态性。你不能存储Node的子类型,它们会被切片。

如果这是一个问题,我建议选择2。存储指针意味着移动指针更快,而多态性起作用。

我认为没有理由选择方案1或您的原始解决方案。

p.s.代码中的this->是不必要的。

正如DyP所指出的,set无论如何都使用堆,这就是选项2的优点。线索-基于堆栈的结构无法增长。=>我相信只有std::array存储在堆栈中。

让我谈谈元问题:您不希望堆栈溢出,从而将数据结构放在堆上。这是正确的做法。但重要的是要理解事情什么时候会被搁置。

每个局部变量都在堆栈上分配。如果您有动态大小的数据结构,那么它们在(大多数)所有情况下都引用堆。(我知道的唯一例外是,当您故意使用alloca()std::get_temporary_buffer()或类似的东西在堆栈上保留内存时)。特别是所有STL容器都将其内存保留在堆上,几乎不使用任何用于局部变量或成员变量的堆栈内存(除了std::array,其大小在编译时已知)。

因此,如果您想节省堆栈内存,将动态大小的数据结构封装到unique_ptrs中效果很小,但它会给程序增加间接性,这会使代码复杂化,降低执行速度,并不必要地增加堆内存使用量。

以下是一个示例:在使用32位编译的Visual Studio 2010上,std::set将在堆栈上使用20字节的内存,而与模板类型参数和集合中包含的实际数字元素无关。集合元素的内存在堆上。

我相信,您现在可以自行决定是否将unique_ptrs用于您想要的目的。

基本上,它还取决于您希望如何访问RoadMap实例中存储的Node实例。我假设您的Node实例将释放封装的note数据。

我会选择调整后的版本2。