unique_ptr 在 class 如何与他们合作
unique_ptr in class how to work with them
我正在用 C++ 实现 AVL 树,并为 children 使用 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
语义才有可能。但那是错误的,因为我只想遍历树,否则它只会从 child-list.
中删除它们
但是,我还需要 unique_ptr
,因为它会传入函数 balance
,因为它会修改指针和 re-balance 树。
如果我使用 shared_ptr
就可以了。但是,我不需要与他人分享所有权。还是我误解了所有权?
你的问题好像是对如何在实际程序中使用unique_ptr
不了解造成的,这与所有权的概念有关。如果一个 something 拥有 一个对象,这意味着,只要 this something 一直拥有该对象,该 something 就负责保持该对象存活,并负责尽快销毁该对象 nothing 不再拥有该对象。
unique_ptr
和shared_ptr
都可以用来拥有对象。您似乎已经意识到,区别在于 unique_ptr
指向的对象只能有一个所有者,而可能有多个 shared_ptr
对象 共享所有权 特定对象。如果 unique_ptr
被销毁或分配不同的值, 根据定义 它可以销毁它先前指向的对象,因为 unique_ptr
是单个(唯一)对象的所有者。
现在您必须考虑您的树:您可以对所有内容使用 shared_ptr
,这很可能(似乎)有效,因为只要有对它们的引用,对象就会保持活动状态。如果 node
中确实存在您在方法中使用但未在 node
结构中声明的 parent
成员,那么您可能会创建引用循环,但会造成危险将对象保存太久(甚至永远,这称为内存泄漏),因为 C++ 中的 shared_ptr
纯粹是引用计数。两个包含 shared_ptr
的对象相互指向对方,即使没有其他指针指向它们,它们也会永远存在。在您的 shared_ptr
解决方案中,parent
成员似乎是一个 weak_ptr
,这是解决此问题的明智方法,尽管可能不是最有效的方法。
您似乎想通过使用 unique_ptr
而不是 shared_ptr
来提高代码的性能和严格性,这被普遍认为是一个非常好的主意,因为它迫使您处理所有权更多细节。您选择树 拥有 根节点,每个节点 拥有 子节点是一个合理的设计。您似乎已经删除了 parent
指针,因为它不能是 unique_ptr
,在这种情况下,节点将由其父节点 和 拥有childrens 它可能有,违反了 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
,你将覆盖你的根节点指针。我确定这不是您想要的。相反,您希望 node 引用与之前引用不同的对象,因此它需要引用根节点并且可以引用其他对象。在 C++ 中,这意味着您需要一个指针(正如 Jarod42 在评论中所说)。循环扫描插入位置有两种选择:
- 使用指向节点的原始指针而不是指向节点的 unique_ptr。由于您不需要所有权,因此指向节点的原始指针就足够了:您可以确保拥有指针 (
this->root
) 在您需要时保持活动状态,因此不存在对象消失的危险。
- 使用指向 unique_ptr 的原始指针到节点。这本质上是您的方法,固定使用指针而不是引用。
正如您所说,稍后您需要 unique_ptr 将其传递给余额函数。如果 balance 函数像现在这样运行,并且需要一个 unique_ptr 参数,那么就会做出决定:在 node
中拥有原始指针的副本不会做你想要的,所以你需要指向 unique_ptr.
的指针
我正在用 C++ 实现 AVL 树,并为 children 使用 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
语义才有可能。但那是错误的,因为我只想遍历树,否则它只会从 child-list.
但是,我还需要 unique_ptr
,因为它会传入函数 balance
,因为它会修改指针和 re-balance 树。
如果我使用 shared_ptr
就可以了。但是,我不需要与他人分享所有权。还是我误解了所有权?
你的问题好像是对如何在实际程序中使用unique_ptr
不了解造成的,这与所有权的概念有关。如果一个 something 拥有 一个对象,这意味着,只要 this something 一直拥有该对象,该 something 就负责保持该对象存活,并负责尽快销毁该对象 nothing 不再拥有该对象。
unique_ptr
和shared_ptr
都可以用来拥有对象。您似乎已经意识到,区别在于 unique_ptr
指向的对象只能有一个所有者,而可能有多个 shared_ptr
对象 共享所有权 特定对象。如果 unique_ptr
被销毁或分配不同的值, 根据定义 它可以销毁它先前指向的对象,因为 unique_ptr
是单个(唯一)对象的所有者。
现在您必须考虑您的树:您可以对所有内容使用 shared_ptr
,这很可能(似乎)有效,因为只要有对它们的引用,对象就会保持活动状态。如果 node
中确实存在您在方法中使用但未在 node
结构中声明的 parent
成员,那么您可能会创建引用循环,但会造成危险将对象保存太久(甚至永远,这称为内存泄漏),因为 C++ 中的 shared_ptr
纯粹是引用计数。两个包含 shared_ptr
的对象相互指向对方,即使没有其他指针指向它们,它们也会永远存在。在您的 shared_ptr
解决方案中,parent
成员似乎是一个 weak_ptr
,这是解决此问题的明智方法,尽管可能不是最有效的方法。
您似乎想通过使用 unique_ptr
而不是 shared_ptr
来提高代码的性能和严格性,这被普遍认为是一个非常好的主意,因为它迫使您处理所有权更多细节。您选择树 拥有 根节点,每个节点 拥有 子节点是一个合理的设计。您似乎已经删除了 parent
指针,因为它不能是 unique_ptr
,在这种情况下,节点将由其父节点 和 拥有childrens 它可能有,违反了 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
,你将覆盖你的根节点指针。我确定这不是您想要的。相反,您希望 node 引用与之前引用不同的对象,因此它需要引用根节点并且可以引用其他对象。在 C++ 中,这意味着您需要一个指针(正如 Jarod42 在评论中所说)。循环扫描插入位置有两种选择:
- 使用指向节点的原始指针而不是指向节点的 unique_ptr。由于您不需要所有权,因此指向节点的原始指针就足够了:您可以确保拥有指针 (
this->root
) 在您需要时保持活动状态,因此不存在对象消失的危险。 - 使用指向 unique_ptr 的原始指针到节点。这本质上是您的方法,固定使用指针而不是引用。
正如您所说,稍后您需要 unique_ptr 将其传递给余额函数。如果 balance 函数像现在这样运行,并且需要一个 unique_ptr 参数,那么就会做出决定:在 node
中拥有原始指针的副本不会做你想要的,所以你需要指向 unique_ptr.