在课堂上的unique_ptr如何使用它们

时间:2015-01-01 09:32:39

标签: c++ c++11 shared-ptr unique-ptr

我正在用C ++实现AVL树,并为孩子们使用unique_ptr

struct Node 
{
    const int key;
    std::unique_ptr<Node> left, right;
    Node* parent;
    std::size_t height; ///< for avl tree.
    Node(const int key) : key(key), height(0) {}
};

class AVL
{
    std::unique_ptr<Node> root;
public:
    AVL(int rootKey) : root(std::unique_ptr<Node>(new Node(rootKey))) {
    }

    void insert(std::unique_ptr<Node> newNode) {
        std::unique_ptr<Node> & node = root;
        Node* parentWeak;
        while(node.get()) {
            parentWeak = node->parent;
            if (node->key == newNode->key)
                throw std::runtime_error("Key already present");
            if (node->key < newNode->key) 
                node = node->right;
            else
                node = node->left;
        }

        auto parent = parentWeak;
        const int key = newNode->key;

        if (parent == nullptr) {
            // there is no root
            root = std::move(newNode);
        } else {
            if (parent->key < newNode->key) {
                assert(NULL == parent->right.get());
                parent->right = std::move(newNode);
            } else {
                assert(NULL == parent->left.get());
                parent->left = std::move(newNode);
            }
        }

        // Now increment the height upto down.
        incrementHeight(key);
        // balance starting from parent upwards untill we find some dislanace in height 
        balance(parent, key); 
    }
};

我在第node = node->right;行收到编译错误。哪个是正确的,因为只有std::move语义才有可能。但这是错误的,因为我想迭代树,否则它只会从子列表中删除它们。

但是,我也需要unique_ptr,因为它会在函数balance中传递,因为它会修改指针并重新平衡树。

如果我使用shared_ptr,那一切都会奏效。但是,我不需要与他人分享所有权。或者我误解了所有权?

1 个答案:

答案 0 :(得分:2)

您的问题似乎是由于缺乏对如何在实际程序中使用unique_ptr的理解造成的,这与所有权的概念有关。如果一个拥有一个对象的东西,这意味着,只要这个东西保持拥有该对象,这个东西负责保持对象存活,并且只要没有任何东西就负责销毁该对象拥有该对象。

unique_ptrshared_ptr都可用于拥有对象。您似乎意识到,差异在于unique_ptr指向的对象只能拥有一个所有者,而可能有多个shared_ptr个对象共享所有权一个特定的对象。如果unique_ptr被销毁或分配了不同的值,按照定义它可以销毁它先前指向的对象,因为unique_ptr是一个(唯一的)所有者对象

现在你必须考虑你的树:你可以使用shared_ptr来处理所有可能(似乎)工作的事情,因为只要存在对它们的引用,对象就会保持活动状态。如果parent中的node成员在您的方法中使用但未在node结构中声明,则可能会创建参考周期,从而产生危险保持对象太长时间(甚至永远,这称为内存泄漏),因为C ++中的shared_ptr纯粹是引用计数。包含shared_ptr s彼此指向的两个对象将永远保持活着,即使没有其他指针指向它们。好像在shared_ptr解决方案中,parent成员是weak_ptr,这是解决此问题的合理方法,尽管可能不是最有效的方法。

您似乎希望使用unique_ptr代替shared_ptr来提高代码的性能和严格性,这通常被认为是一个非常好的主意,因为它迫使您更大程度地处理所有权详情。您选择树拥有根节点,并且每个节点拥有子节点是一种合理的设计。您似乎已删除了parent指针,因为它不能是unique_ptr,因为在这种情况下,一个节点将由其父级拥有它可能的任何子级违反了unique_ptr指向的对象可能只有一个所有者的约束。此外,parent成员不能是weak_ptr,因为weak_ptr只能与shared_ptr管理的对象一起使用。如果您要将设计从shared_ptr转换为unique_ptr,则应考虑将weak_ptr更改为原始指针。不存在指向由unique_ptr管理的对象的非拥有指针,该对象检测到该对象的到期(通过典型的C ++内存管理,它无法实现)。如果您需要能够检测到非拥有指针的属性,请继续使用shared_ptr。跟踪非拥有指针的开销几乎与完全共享所有权语义一样大,因此标准库中没有中间地带。

最后,让我们讨论insert方法。 node变量肯定不是你想要的。您正确地发现(可能是编译器错误消息)node不能是unique_ptr,因为这会从树对象中夺走所有权。实际上,让这个变量引用树中的根指针是正确的解决方案,因为你现在不想弄乱所有权,但只是希望能够抓住某个节点。但是将其声明为引用并不适合您想要使用它的方式,因为在C ++中您无法重新定位引用。你所做的就是为node声明this->root 只是另一个名字,所以如果你分配给node,你将覆盖你的根节点指针。我确信这不是你的意图。相反,您希望节点引用与之前引用的不同的对象,因此它需要引用根节点并且可以引用其他内容。在C ++中,这意味着你需要一个指针(如注释中的Jarod42所述)。您有两个选择可以循环扫描插入位置:

  • 使用节点的原始指针而不是节点的unique_ptr。由于您不需要所有权,因此指向节点的原始指针就足够了:您可以确保拥有指针(this->root)在您需要时保持活动状态,因此不存在对象的危险消失。
  • 使用unique_ptr到节点的原始指针。这基本上是你的方法,固定使用指针而不是引用。

正如您所说,您稍后需要将unique_ptr传递给balance函数。如果balance函数现在可以正常工作,并且需要unique_ptr参数,那么就做出决定:在node中拥有原始指针的副本并不能做你想要的,所以你需要指针到的unique_ptr。