3

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

我目前的设计是这样的:

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许多析构函数delete时需要调用 s。此外,RoadMap调用者在添加节点时必须提供 a unique_ptr,这意味着他必须自己进行堆分配。


现在,我更喜欢选项 1 而不是其他选项。但是,我只在相对较短的时间内编写 C++ 代码,并且不确定我是否完全理解内存管理背后的概念,这就是为什么我希望您(in)验证我的观点。假设选项 1 是最好的方法,我是否正确?您对此类事情的最佳实践有任何其他参考吗?

4

4 回答 4

5

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

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));
    }
}
于 2013-08-20T07:45:09.257 回答
1

他们每个人都彼此完全不同。

为简单起见,我建议选项 2。但是在诸如 etc 之类的某些操作中它可能会更加性能密集,sort因为您将移动整个Node而不是指向它的指针。

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

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

如果这是一个问题,我会建议选项 2。存储指针意味着移动它们更快,并且多态性有效。

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

psthis->在您的代码中是不必要的。

pps 正如 DyP 指出的那样set,无论如何都使用堆,这就是使选项 2 好的原因。线索 - 基于堆栈的结构不能增长。=> 只有std::array我相信存储在堆栈中。

于 2013-08-20T07:29:02.643 回答
1

让我谈谈元问题:您不希望堆栈溢出,因此将数据结构放在堆上。这是正确的做法。但是这里要了解的重要一点是什么时候将事情放到堆上。

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

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

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

我相信,您现在可以自行决定是否将unique_ptrs其用于您的意图。

于 2013-08-20T13:59:04.490 回答
0

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

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

于 2013-08-20T07:36:13.507 回答