我有这样的代码
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”调用了它的析构函数,但我不明白为什么。 范围还没有结束。
paths.push_back(*path);
这可以做一些事情。
>
它从堆分配的*path
对象构造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;
}
您仍然会在行中调用路径析构函数(实际上,您会得到一个额外的),但代码不会泄漏。 假设您的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
对象的副本,这可能是值得避免的,具体取决于它的编写方式。