我们有一个非常标准的树API,使用共享指针,看起来大致相似(为简洁省略了实现):
class node;
using node_ptr = std::shared_ptr<node>;
class node : public std::enable_shared_from_this<node> {
std::weak_ptr<node> parent;
std::vector<node_ptr> children;
public:
virtual ~node() = default;
virtual void do_something() = 0;
void add_child(node_ptr new_child);
void remove_child(node_ptr child);
node_ptr get_parent();
const std::vector<node_ptr>& get_children();
};
class derived_node : public node {
derived_node() = default;
public:
virtual void do_something() override;
static node_ptr create(/* args... */);
};
// More derived node types...
这很好用,可以防止节点泄露,就像你想象的那样。但是,我已经阅读了关于SO的各种其他答案,在这样的公共API中使用std::shared_ptr
被认为是不好的风格,应该避免使用。
显然,这会冒险进入基于意见的领域,所以要避免这个问题被关闭的几个具体问题: - )
在这样的接口中使用shared_ptr
是否有任何众所周知的陷阱,到目前为止我们有幸避免这种陷阱?
如果是这样,是否有一种常用的(我犹豫说“惯用”)替代配方,避免了上述陷阱,但仍允许用户进行简单的内存管理?
感谢。
答案 0 :(得分:2)
它的风格不错,取决于你的目标和假设。
我使用硬限制工作的一些项目要求我们避免使用shared_ptrs,因为我们想管理自己的内存。因此,需要使用需要使用shared_ptrs的第三方库。
您可能希望避免使用shared_ptrs的另一个原因是它有些自以为是。有些项目会包装它周围的所有内容,只是假装它有一种GC语言(Urg!)。其他项目会更加谨慎地对待shared_ptrs,只有在实际拥有共享所有权的东西时才使用shared_ptr。
我使用过的大多数第三方API(当然不是全部)都按照原则进行操作,如果你已经分配了它,你会破坏它。只要您非常清楚资源的所有权,它就不会引起太多问题。
答案 1 :(得分:2)
std::shared_ptr
是管理所有权,
所以更喜欢print_tree
功能
void print_tree(const node& root); // no owner ship transfer
大于
void print_tree(const std::shared_ptr<node>& root);
后者需要shared_ptr
,因此可能需要从对象构造shared_ptr
。 (而从shared_ptr
检索对象是一个简单的getter)
现在,对于 getters ,您主要可以选择
share_ptr
,如果您想与用户共享所有权weak_ptr
,对内部的安全引用安全和不安全,我的意思是如果对象被破坏,
您可以使用weak_ptr
测试,但不能使用简单的指针。
虽然安全性有一些开销,所以需要权衡。
如果访问者仅供本地使用而不保留对它的引用,则指针/引用可能是一个不错的选择。
例如,一旦向量被销毁,std::vector::iterator
就无法使用,因此有利于本地使用,但保留迭代器作为参考可能是危险的(但可能)。
您是否期望/允许该用户保留节点的引用但允许销毁根(或父)?该节点应该为用户带来什么?
对于void add_child(node_ptr new_child);
,您显然拥有所有权。如果节点将其构建为子项本身,则可以隐藏shared_ptr
,如
template <typename NodeType, typename...Ts>
std::weak_ptr<NodeType> add_child(Ts&&... args)
{
auto n = std::make_shared<NodeType>(std::forward<Ts>(args)...);
// or
// auto n = NodeType::create(std::forward<Ts>(args)...);
// Stuff as set parent and add to children vector
return n;
}
答案 2 :(得分:1)
这可能被视为错误样式的一个原因可能是引用计数中涉及的额外开销(通常(永远不会说永远不会)在外部API中实际上不需要),因为它们通常属于以下两类之一:
std::unique_ptr
通常更合适,并且有0开销。