【问题标题】:Better option than `shared_ptr` for "widget" tree structure?比“小部件”树结构的“shared_ptr”更好的选择?
【发布时间】:2017-11-18 12:41:45
【问题描述】:

在现代 C++ 中维护“小部件”树的最佳方法是什么?目前,我对子节点使用vectorshared_ptr,并使用指向父节点的普通指针。它是这样使用的:

auto canvas = make_shared<Element>("canvas");
auto button = make_shared<Element>("button");
canvas->addChild(button);
// do something with either canvas or button

我很满意,但我不喜欢make_shared&lt;Element&gt; 仪式的必要性。

我还读到您 should use unique_ptr 并让父元素拥有子元素。我同意一般情况,但如果你有一个“小部件”树,我不确定它是如何工作的。对于“小部件”树,我的意思是像你在例如图形用户界面代码。它是通过在这里和那里添加节点来手动构建的,并且外部代码经常必须访问单个节点。如果我使用unique_ptr,对addChild 的调用将移动指针,上面的代码将不再能够使用子对象。我可以让addChild 再次返回一个常规指针,但这会使调用站点更加复杂。

理想情况下,我可以这样说:

Element blah() {
    // this could also be changed to Element* or some_pointer<Element>,
    // but I want the call site to be clean and simple and not leak
    // implementation details
    Element canvas("canvas");
    Element button("button");
    canvas.addChild(button);
    button.manipulate();
    return canvas;
}

auto canvas = blah();
canvas.children[0].manipulate();

当然,这在 C++ 中是行不通的。要么addChild 复制,但随后我在“blah”中操作了错误的按钮实例。或者它需要一个指针,但随后它变得悬空并且操作失败。我需要一个神奇的操作,将button 的所有权转移到canvas,并将button 替换为指向真实按钮的外壳对象。为此,我必须将 Element 替换为一种伪造引用语义的智能容器,并将真实对象隐藏在其中(我认为 C++/WinRT 会这样做)。但是,这似乎很单调,这就是我选择shared_ptr 的原因。我还缺少其他成语吗?

【问题讨论】:

  • 你为什么不喜欢make_shared&lt;Element&gt;,就要把鼹鼠山变成一座山?
  • @EdHeal:我发现在这种情况下可以使用不同的模式,我想了解每种模式的优缺点。这是我想出的,但是我读到有人建议不要以这种方式使用shared_ptr。也许这很好,也许有更好的方法。我不是“以鼹鼠山造山”。没有大问题,我没有抱怨,只是想学习一些东西并提高我的手艺。
  • 如果您需要对对象的生命周期进行如此明确的控制,我认为智能指针只是错误的方法。我只使用std::vector&lt;Element*&gt; children,防止复制,确保将deletes 很好地封装在Element 的析构函数中,并确保每个Element 都必须使用Parent 指针参数构造。我也不会提供addChild;永远不应该有一个Element 不附属于父母,甚至是短暂的。
  • @ChristianHackl:问题是,我不需要显式控制对象的生命周期。我对生命的担忧越少越好。我想尽可能避免出现悬空或无效指针。我需要的是拥有未附加元素的能力,或者分离子树,使用它,然后在其他地方附加它。想想 DOM 节点。如果不是这样,我会采用你的方法。
  • 看看 Qt 如何处理这个问题可能很有启发性。

标签: c++


【解决方案1】:

您可以使addChild 的行为类似于emplace 方法,并让它在内部创建一个unique_ptr,然后返回一个非拥有指针(或引用)。 IE。类似:

template <class... Args>
Element & Element::addChild (Args && ... args) {
  children_.push_back(std::make_unique<Element>(std::forward<Args>(args)...));
  return children_.back();
}

所以现在您可以在不暴露调用站点的任何指针的情况下执行以下操作:

Element blah () {
  Element canvas("canvas");
  Element & button = canvas.addChild("button");
  button.manipulate();
  return canvas;
}

编辑:addChild的代码更简洁。

【讨论】:

    猜你喜欢
    • 2015-07-21
    • 2018-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多