对于树结构"widget"比"shared_ptr"更好的选择?

Better option than `shared_ptr` for "widget" tree structure?

本文关键字:更好 ptr 选择 shared 于树 结构 widget      更新时间:2023-10-16

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

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

我很满意,但我不喜欢必要的make_shared<Element>仪式。

我还读到,您应该使用unique_ptr,并让父元素拥有子元素。我同意一般情况,但如果你有一个"小部件"树,我不确定它会如何工作。对于"小部件"树,我指的是像GUI代码中那样的树。它是通过在这里和那里添加节点来手动构建的,外部代码经常必须访问单个节点。如果我使用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的原因。我还缺少其他成语吗?

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

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代码更加简洁。