【问题标题】:Best way to delete parent/child window hierarchy删除父/子窗口层次结构的最佳方法
【发布时间】:2017-08-23 14:13:05
【问题描述】:

我在 GUI 中有一个窗口系统,但我不确定销毁顺序。每个 Window 都有一个包含其子级的向量,每个 Window 也有一个指向其父级的指针:

auto root = new Window;
root->addChild(new Window);
root->addChild(new Window);
auto child = root->addChild(new Window);  // Return value is the newly created Window
child->addChild(new Window);
child->addChild(new Window);
auto grandchild = child->addChild(new Window);
grandchild->addChild(new Window);
grandchild->addChild(new Window);
grandchild->addChild(new Window);

// 我想删除子指针,首先我需要从它的父子向量中删除它的指针。

child->parent->children.erase(child->parent->children.begin() + child->positionInParentsVector - 1);

child->destroy();

void Window::destroy()
{
    if (children.size() == 0) delete this;
    else for (auto i : children) i->destroy();
}

或者使用智能指针向量,这样做是否足够:

// Remove reference of child from parent's children vector, then

delete child;

我已经读到删除它是可以的。我很难理解这一点。

【问题讨论】:

  • 智能指针可能是最好的,所以它会自动清理。但是,如果您的层次结构 非常 深,您可能会在析构函数中遇到堆栈溢出。在这种情况下,您将需要像您展示的那样“手动”发布所有内容。
  • 另外,因为destroy函数是递归的,我只需要在最高级别删除父级的引用一次,这意味着我需要创建一个函数来删除引用,另一个函数来删除引用做递归删除,对吗?那可能是三个函数,一个是 removeReferenceAndDestroy,一个是 removeReference,然后执行递归 destroy()。没有更优雅的方式了吗?
  • 如果堆栈溢出 一个现实问题(它也会发生在常规层次递归遍历中)那么它实际上 不是 像你展示的那样,你需要不用递归而是用循环来做。
  • 基本上智能指针 dtor 会自动执行您所做的操作。
  • 如果你有一个addChild,你不应该有一个相应的removeChild 让父母进行清理吗?对我来说,child->destroy() 似乎是从delete child 调用时析构函数应该做的事情(但没有delete this)。

标签: c++ vector parent hierarchy


【解决方案1】:

我觉得你有点搞糊涂了。以下自包含示例没有内存泄漏:

#include <utility>
#include <vector>
#include <memory>

using std::vector;
using std::unique_ptr;

struct Window {
    Window() = default;
    vector<unique_ptr<Window>> children;
    Window* parent = nullptr;
    ~Window();

    Window* addChild(std::unique_ptr<Window> c) {
        c->parent = this;
        children.push_back(std::move(c));
        return children.back().get();
    }
};

Window::~Window() = default;

int main() {
    auto root = std::make_unique<Window>();
    root->addChild(std::make_unique<Window>());
    auto child = root->addChild(std::make_unique<Window>());
    child->addChild(std::make_unique<Window>());
};

一个类不能包含它自己,但它可以包含一个unique_ptr 给它自己(或者它的容器)。在这个方案中,所有的递归销毁都正确且自动地发生。当然,你可以用各种方式来装扮它,但这是基本理念。

这里还有一些关于良好的对象和类设计和封装的问题,但这是另一个问题。注意:在绝大多数情况下,您不应该有一个名为 destroy 的方法,而是应该在析构函数中发生。

基本上不需要在 C++11 或更高版本中编写 new 或 delete,除非您正在编写相对较低级别的代码(如您自己的内存分配器)。

现场示例:http://coliru.stacked-crooked.com/a/adef8cc40de4916a

【讨论】:

  • 谢谢。有趣的是,大多数人倾向于远离原始指针,以及 C++ 的趋势如何。我想这对于自动清理来说是一件好事,绝对容易得多。智能指针向量总是一个额外的调用,对吧?在销毁向量后,每个 unique_pointer 的析构函数都会被调用,然后在指向的对象上调用 delete。
  • @Zebrafish 你必须小心。人们远离拥有原始指针。我的代码包含原始指针成员变量(通常是坏事,但有时您需要它)和从成员变量返回的指针。但是,在这两种情况下,它都是非所有权关系。请注意,在addChild 中,最好检查c 是否确实拥有Window,如果没有则抛出。然后你可以返回一个Window&amp; 更好,因为它不能为空(如果你好奇,我可以更改代码)。
  • @Zebrafish 一个额外的电话,相对​​于什么?您的描述是正确的,但我看不出如果使用手动而不是自动清理编码会更快。
  • 当你 std::make_unique 它消息一个新窗口时,它怎么能不包含一个窗口呢?你的意思是如果分配失败?另外我不明白为什么返回对 Window 的引用而不是指针会有所不同。
  • @Zebrafish 我不确定您的第一条评论。我说“一个类不能包含自己”。一个 Window 没有 Window 成员,但它可以有一个 unique_ptr 成员。 unique_ptr 有一个间接的方法,这使得这项工作。引用更好,因为它不能为空,并且拥有空窗口并没有任何意义。就像如果有人打电话给root-&gt;addChild(std::unique_ptr&lt;Window&gt;{});,你可能希望它抛出一个异常。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-17
  • 2018-11-14
  • 1970-01-01
  • 2014-01-24
  • 2011-04-23
  • 2023-03-14
相关资源
最近更新 更多