【问题标题】:Using std::shared_ptr in pointer to pointer scenario在指针场景中使用 std::shared_ptr
【发布时间】:2018-08-01 23:25:14
【问题描述】:

我创建了一个Node 结构,用于实现二叉搜索树。它使用共享指针来跟踪其子代:

template <class T> struct Node;

template <class T>
using Node_ptr = std::shared_ptr<Node<T>>;

template <class T>
struct Node {
  Node_ptr<T> left;
  Node_ptr<T> right;
  const T label;
  ...
};

现在,我想要一个函数,它给出一些子树,一个值将返回该特定值的节点或该节点将来应该位于的位置 - find_node

现在是这样的:

template <class T>
auto* find_node(Node_ptr<T>* node, const T& value) {
  for (; *node && (*node)->label != value
       ; node = value < (*node)->label ? &(*node)->left : &(*node)->right);

  return node;
}

相当糟糕。但它有效:

template <class T>
class Binary_search_tree {
public:
  // correctly inserts consecutive values
  void insert(const T& value) {
    if (auto* node = find_node(&root, value); !*node)
      *node = std::make_shared<Node<T>>(value);
  }
  ...
private:
  Node_ptr<T> root;
  ...
};

我可以重写find_node 以使用std::shared_ptr&lt;Node_ptr&lt;T&gt;&gt; 而不是Node_ptr&lt;T&gt;*,但它看起来会更糟。还是会?

我应该如何处理这种情况?

编辑:正如已经指出的那样,可以通过引用起始节点并返回对节点的引用来简化函数:

template <class T>
Node_ptr<T>& find_node(Node_ptr<T>& node_ref, const T& value) {
  auto* node = &node_ref;
  ...
  return *node;
}

【问题讨论】:

  • 由于您没有直接分配Node_ptr&lt;T&gt; *,您所做的可能看起来“很糟糕”,但它也有效,我认为没有理由包装共享在这种情况下,共享指针周围的指针。如果您一直在分配和存储指向共享指针的指针,那将会改变事情。综上所述,您有什么理由不使用 reference 返回类型?在您提出的情况下,我认为node 永远不会为NULL。事实上,通过的node 也将是参考候选。
  • 你真的需要shared_ptr吗?您是否在不同的树或其他东西之间共享节点
  • @WhozCraig 谢谢!实际上,起始节点和返回的节点都可以通过引用传递。我完全忽略了这个事实,更关注函数体。但正如您所指出的,在使用原始指针的方式中,它们通常不会带来与它们相关的风险。也许我只是挖得太深了:)
  • 如果您不在树之间共享注释或允许将一个相同的节点多次插入到一棵树中,那么您只能在一种情况下(节点删除)从智能指针中受益,而在所有其他情况下(插入、移动节点,例如用于树平衡,...),您将有相当多的开销,因为除了修改树之外,您实际上不会共享指针(无论如何,所有涉及的节点都是已知的... )。那么也许std::unique_ptr 是一个更好的选择?

标签: c++ shared-ptr c++17 smart-pointers


【解决方案1】:
  1. 当您必须允许传递空指针时,建议使用原始指针 - 在您的示例中不是这样;或者当您必须传递非整数值时(在您的情况下为 true)。在后一种情况下,仍应考虑传递引用而不是原始指针。这是一个通用建议 - 因此可能存在例外情况。

  2. 已经注意到,您仍然可以在函数中使用原始指针,方法是让 find_node(...) 成为私有函数,同时保持 insert(...) 公开。这是安全的,因为没有机会让指针从 insert(...) 内部悬空。

本质上,我们需要防止使用原始指针的两种可能性:#1。过早地删除指针指向的内存,#2。永远不要删除指针指向的内存。这在您的 insert(...) 函数中都是不可能的。所以你很安全。

在相关说明中,您可能会考虑在创建节点时为其设置 unique_pointer,然后如果它们要由多个子节点共享,则将它们转换为共享指针:std::move(...)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-07
    • 1970-01-01
    • 1970-01-01
    • 2013-08-18
    • 2023-01-12
    • 2012-08-20
    • 1970-01-01
    相关资源
    最近更新 更多