我有一些这样的代码
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;
}
Run Code Online (Sandbox Code Playgroud)
问题似乎是 std::vector "paths" 调用了它的析构函数,但我不明白为什么。范围还没有结束。
paths.push_back(*path);
Run Code Online (Sandbox Code Playgroud)
这有几件事情。
它Path从堆分配的*path对象构造一个副本。
它调整paths向量的大小。这有时可能只涉及增加一个整数,但有时它涉及移动每个现有Path对象并销毁旧对象。
在第一点,你有一个泄漏。 new在空闲存储上分配对象,您始终负责确保它们被清理。将空闲存储中的对象复制到向量中并不会清除空闲存储中的对象。
在第二点中,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;
}
Run Code Online (Sandbox Code Playgroud)
您仍然会在线上调用路径析构函数(实际上,您会得到一个额外的析构函数),但您的代码不会泄漏。假设您的移动构造函数是正确的(并且实际上正确移动路径的状态),一切都应该正常工作。
现在更高效的版本如下所示:
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;
}
Run Code Online (Sandbox Code Playgroud)
它摆脱了您正在处理的所有临时变量,并在不再使用时移动资源。
假设有效的移动构造函数,这里的最大收获是我们预先保留了路径向量(节省了 lg(n) 内存分配)并将段向量移动到路径的构造函数中(如果写得正确,可以避免不必要的复制段指针的缓冲区)。
这个版本也没有在有问题的行上调用析构函数,但我认为这不是特别重要;空路径的析构函数的成本应该几乎是免费的,甚至可以优化掉。
我也可能避免了path_node对象的副本,这可能值得避免,具体取决于它的编写方式。