gpt4 book ai didi

c++ - 为什么 std::vector 在离开不同范围时调用析构函数?

转载 作者:行者123 更新时间:2023-12-01 14:04:34 27 4
gpt4 key购买 nike

我有这样的代码

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“路径”调用了它的析构函数,但我不明白为什么。范围还没有结束。

最佳答案

paths.push_back(*path);

这做了一些事情。

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

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

在第一点,你有一个漏洞。 new 在空闲存储区分配对象,您始终负责确保它们被清理。将对象从自由存储区复制到 vector 中不会清除自由存储区中的对象。

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

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

您似乎是 C# 或 Java 程序员。在这两种语言中,对象的实际值真的很难创建——它们希望保留对对象的垃圾收集引用。在这些语言中,对象数组实际上是一组对对象的引用。在 C++ 中,对象 vector 是一个实际包含相关对象的 vector 。

您对 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;
}

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

现在更高效的版本如下所示:

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;
}

它摆脱了您正在处理的所有临时变量,并在资源不再使用时移动它们。

假设有高效的移动构造函数,这里最大的好处是我们预先保留了路径 vector (节省 lg(n) 内存分配)并且我们将段 vector 移动到路径的构造函数中(如果写得正确,可以避免段指针缓冲区的不必要拷贝)。

这个版本也没有在有问题的行上调用析构函数,但我认为这不是特别重要;空路径的析构函数的成本应该几乎是免费的,甚至可以优化掉。

我也可能避免了 path_node 对象的拷贝,这可能值得避免,具体取决于它的编写方式。

关于c++ - 为什么 std::vector 在离开不同范围时调用析构函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62304795/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com