为什么std :: vector在离开另一个作用域的同时调用析构函数?

问题描述 投票:3回答:1

我有一些这样的代码

std::vector<cuarl_path::Path> RoverPlanner::get_paths(const char *filename) const
{
    pugi::xml_document doc;
    doc.load_file(filename);
    pugi::xml_node root = doc.document_element();

    std::vector<cuarl_path::Path> paths;
    for (auto path_node : root.children()) {
        std::vector<cuarl_path::Segment*> segments;
        for (auto segment_node : path_node.children())
        {
            //do stuff to populate the `segments` vector
        }

        cuarl_path::Path* path = new cuarl_path::Path(segments);
        paths.push_back(*path); //Path destructor called here
    }

    return paths;
}

这里是调用堆栈enter image description here

问题似乎是std :: vector“路径”调用了它的析构函数,但我不明白为什么。范围尚未结束。

c++ pointers
1个回答
2
投票
paths.push_back(*path);

这会做一些事情。

  1. 它从堆分配的Path对象构造一个*path副本。

  2. 它调整paths向量的大小。有时这可能仅涉及增加一个整数,但有时涉及移动每个现有的Path对象并销毁旧的对象。

首先,您有泄漏。 new在免费存储区上分配对象,您始终负责确保将其清除。将对象从免费存储中复制到向量中会not清理免费存储中的对象。

第二点,vector实际上保存实际对象,而不是对它们的引用。因此,当您调整缓冲区的大小(push_back可以执行此操作)时,必须四处移动对象的值,然后清理要丢弃的对象的值。

清理是您正在执行的析构函数。

您似乎是C#或Java程序员。在这两种语言中,都很难创建对象的实际值,它们想保留垃圾回收的对象引用。对象数组实际上是这些语言中的对对象的引用的数组。在C ++中,对象向量是向量实际上包含有关对象的向量。

您对new的使用也是一个提示。那里不需要new,也不需要指针:

std::vector<cuarl_path::Path> RoverPlanner::get_paths(const char *filename) const
{
    pugi::xml_document doc;
    doc.load_file(filename);
    pugi::xml_node root = doc.document_element();

    std::vector<cuarl_path::Path> paths;
    for (auto&& path_node : root.children()) {
        std::vector<cuarl_path::Segment*> segments;
        for (auto segment_node : path_node.children())
        {
            //do stuff to populate the `segments` vector
        }

        cuarl_path::Path path = cuarl_path::Path(segments);
        paths.push_back(std::move(path)); //Path destructor called here
    }

    return paths;
}

您仍然会在行上调用路径析构函数(实际上,您会得到一个额外的),但是您的代码不会泄漏。假设您的move构造函数正确(并且实际上正确地移动了路径的状态),那么一切都会正常。

现在更高效的版本如下:

std::vector<cuarl_path::Path> RoverPlanner::get_paths(const char *filename) const
{
    pugi::xml_document doc;
    doc.load_file(filename);
    pugi::xml_node root = doc.document_element();

    std::vector<cuarl_path::Path> paths;
    auto&& children = root.children();
    paths.reserve(children.size());
    for (auto path_node : children) {
        std::vector<cuarl_path::Segment*> segments;
        for (auto segment_node : path_node.children())
        {
            //do stuff to populate the `segments` vector
        }

        paths.emplace_back(std::move(segments));
    }

    return paths;
}

它消除了您要弄乱的所有临时变量,并在不再使用它们时移动资源。

假设有效的move构造函数,在这里最大的收获是我们预先重新获得了路径向量(节省了lg(n)内存分配),并将段向量移入了路径的构造函数(如果写得正确,可以避免段指针缓冲区的不必要副本)。

此版本也没有在相关行上调用析构函数,但我认为这不是特别重要;空路径的析构函数的成本应该几乎是免费的,甚至可以合理地消除。

我还可能避免了path_node对象的副本,根据其编写方式,可以避免使用它。

© www.soinside.com 2019 - 2024. All rights reserved.