我有一些这样的代码
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;
}
问题似乎是std :: vector“路径”调用了它的析构函数,但我不明白为什么。范围尚未结束。
paths.push_back(*path);
这会做一些事情。
它从堆分配的Path
对象构造一个*path
副本。
它调整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
对象的副本,根据其编写方式,可以避免使用它。