出于学习目的,我现在正在编码二叉搜索树旋转。 我通常用
std::unique_ptr
,但这次我用std::shared_ptr
这工作正常:
// Node implementation
template <Containable T = int> struct Node {
T key_ = T{};
bool black_ = false; // red-black tree
std::shared_ptr<Node> left_;
std::shared_ptr<Node> right_;
};
// this is a protected member function of red-black tree class
// xp is parent node of x
void left_rotate(Node *xp, Node* x) {
assert(x);
auto y = x->right_;
x->right_ = y->left_;
std::shared_ptr<Node> x_ptr;
if (!xp) {
x_ptr = root_;
root_ = y;
} else if (x == xp->left_.get()) {
x_ptr = xp->left_;
xp->left_ = y;
} else {
x_ptr = xp->right_;
xp->right_ = y;
}
y->left_ = x_ptr;
}
这会崩溃:
void left_rotate(Node *xp, Node* x) {
assert(x);
auto y = x->right_;
x->right_ = y->left_;
std::shared_ptr<Node> x_ptr(x);
if (!xp) {
root_ = y;
} else if (x == xp->left_.get()) {
xp->left_ = y;
} else {
xp->right_ = y;
}
y->left_ = x_ptr;
}
cppreference 说:链接
是一个智能指针,通过指针保留对象的共享所有权。多个shared_ptr对象可能拥有同一个对象。当发生以下任一情况时,该对象将被销毁并释放其内存:std::shared_ptr
- 拥有该对象的最后剩余的shared_ptr被销毁;
- 拥有该对象的最后一个剩余的shared_ptr通过operator=或reset()分配另一个指针。
为了避免在分配之前破坏
x
指向的节点,我创建了另一个拥有std::shared_ptr<Node>
的*x
,但在第二个实现中,x
指向的节点对象在调用y->left_ = x_ptr
之前就已经被破坏了。当调用 root_ = y
、xp->left_ = y
和 xp->right_ = y
之一时,节点对象实际上被销毁。
显然有多个
std::shared_ptr
对象拥有相同的节点对象。 root_
、xp->left_
或 xp->right_
显然不是最后一个拥有该对象的 std::shared_ptr
。为什么会出现这种情况?
void left_rotate(Node *xp, Node* x) {
...
std::shared_ptr<Node> x_ptr(x);
...
当您创建
shared_ptr
时,它会接管 x
的所有权。这里的问题是,这不是你可以给予的。其他人,另一个shared_ptr
,已经拥有了指针的所有权。因此,您在 y->left = xptr
之前执行的一些操作将为拥有 shared_ptr
的 x
分配一个新值,并删除 x
。
问题在于您使用原始指针作为参数。每当您从
shared_ptr
中提取指针时,都要非常小心该对象的生命周期。提取的原始指针不会使对象保持活动状态。对于函数调用来说,一些事情变得极其难以推理。正如您所经历的那样,很容易搞砸。
通过传递
shared_ptr
作为参数可以轻松避免这种情况,因为它们会让你的对象保持活动状态:
void left_rotate(std::shared_ptr<NodeY> xp, std::shared_ptr<Node> x) {
PS:我希望你的
root_
也是shared_ptr
。