我正在用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
,那一切都会奏效。但是,我不需要与他人分享所有权。或者我误解了所有权?
答案 0 :(得分:2)
您的问题似乎是由于缺乏对如何在实际程序中使用unique_ptr
的理解造成的,这与所有权的概念有关。如果一个拥有一个对象的东西,这意味着,只要这个东西保持拥有该对象,这个东西负责保持对象存活,并且只要没有任何东西就负责销毁该对象拥有该对象。
unique_ptr
和shared_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所述)。您有两个选择可以循环扫描插入位置:
this->root
)在您需要时保持活动状态,因此不存在对象的危险消失。正如您所说,您稍后需要将unique_ptr传递给balance函数。如果balance函数现在可以正常工作,并且需要unique_ptr参数,那么就做出决定:在node
中拥有原始指针的副本并不能做你想要的,所以你需要指针到的unique_ptr。