BST with std::unique_ptr:在参数中使用右值引用有什么区别?
BST with std::unique_ptr: What's difference in using rvalue reference in parameters?
为了练习,我正在玩二叉搜索树和带有智能指针的红黑树。
节点如下:
template <typename T>
struct BSTNode {
T key;
std::unique_ptr<BSTNode<T>> left;
std::unique_ptr<BSTNode<T>> right;
BSTNode<T>* succ;
BSTNode(const T& key) : key {key}, succ {nullptr} {}
};
在这里,succ
表示顺序继承,因为我正在解决 CLRS 12.3-5,它说在没有 link 父级的情况下进行练习。
无论如何,这段代码运行愉快:
void Transplant(BSTNode<T>* u, std::unique_ptr<BSTNode<T>>&& v) {
auto p = Parent(u);
if (!p) {
root = std::move(v);
} else if (u == p->left.get()) {
p->left = std::move(v);
} else {
p->right = std::move(v);
}
}
void Delete(BSTNode<T>* z) {
if (!z) {
return;
}
BSTNode<T>* pred = nullptr;
if (z->left) {
pred = Maximum(z->left.get());
} else {
auto y = Parent(z);
auto x = z;
while (y && x == y->left.get()) {
x = y;
y = Parent(y);
}
pred = y;
}
pred->succ = z->succ;
if (!z->left) {
Transplant(z, std::move(z->right));
} else if (!z->right) {
Transplant(z, std::move(z->left));
} else {
auto y1 = z->right.get();
std::unique_ptr<BSTNode<T>> y;
if (!y1->left) {
y = std::move(z->right);
} else {
while (y1->left) {
y1 = y1->left.get();
}
y = std::move(Parent(y1)->left);
}
if (Parent(y.get()) != z) {
Transplant(y.get(), std::move(y->right));
y->right = std::move(z->right);
}
y->left = std::move(z->left);
Transplant(z, std::move(y));
}
}
相比之下,当我在子程序“Transplant”中删除右值引用时,它给出了段错误:
void Transplant(BSTNode<T>* u, std::unique_ptr<BSTNode<T>> v) { // crashes
auto p = Parent(u);
if (!p) {
root = std::move(v);
} else if (u == p->left.get()) {
p->left = std::move(v);
} else {
p->right = std::move(v);
}
}
崩溃发生在Delete
、Transplant(z, std::move(z->right));
的第18行。
这里有什么区别?有人告诉我 std::unique_ptr
通常按值传递。
此外,没有右值引用的Transplant
代码已经在BST代码中工作,即一个节点对其父节点有一个link。我不明白为什么。
当你按值传递一个unique_ptr
时,你失去了原来的指针:
void use_ptr(std::unique_ptr<int>) {}
std::unique_ptr<int> ptr = std::make_unique<int>(999);
use_ptr(std::move(ptr));
// here ptr no longer contains a pointer to 999
assert(!ptr);
std::cout << *ptr; // this is UB
当你通过右值引用传递它时,原始指针在函数调用后保持非空:
void use_ptr(std::unique_ptr<int>&&) {}
std::unique_ptr<int> ptr = std::make_unique<int>(999);
use_ptr(std::move(ptr));
// ptr still manages a pointer to `999`
assert(ptr);
std::cout << *ptr; // this is not UB
std::move
本身不会移动任何东西,它只是使某些东西成为 xvalue 的演员。当您从那个 xvalue 构造 std::unique_ptr<int>
参数时,实际的移动发生了——一旦您构造了一个新的 std::unique_ptr<int>
,您就失去了原来的那个。当你通过 &&
时,你不会构造任何东西,你只是传递一个引用。
考虑这个电话:
Transplant(z, std::move(z->right));
有
void Transplant(BSTNode<T>*, std::unique_ptr<BSTNode<T>>)
在 Transplant
里面,z->right
是一个 unique_ptr
没有任何东西,z->right.get() == nullptr
,因为你刚从它移到第二个 Transplant
参数。
为了练习,我正在玩二叉搜索树和带有智能指针的红黑树。
节点如下:
template <typename T>
struct BSTNode {
T key;
std::unique_ptr<BSTNode<T>> left;
std::unique_ptr<BSTNode<T>> right;
BSTNode<T>* succ;
BSTNode(const T& key) : key {key}, succ {nullptr} {}
};
在这里,succ
表示顺序继承,因为我正在解决 CLRS 12.3-5,它说在没有 link 父级的情况下进行练习。
无论如何,这段代码运行愉快:
void Transplant(BSTNode<T>* u, std::unique_ptr<BSTNode<T>>&& v) {
auto p = Parent(u);
if (!p) {
root = std::move(v);
} else if (u == p->left.get()) {
p->left = std::move(v);
} else {
p->right = std::move(v);
}
}
void Delete(BSTNode<T>* z) {
if (!z) {
return;
}
BSTNode<T>* pred = nullptr;
if (z->left) {
pred = Maximum(z->left.get());
} else {
auto y = Parent(z);
auto x = z;
while (y && x == y->left.get()) {
x = y;
y = Parent(y);
}
pred = y;
}
pred->succ = z->succ;
if (!z->left) {
Transplant(z, std::move(z->right));
} else if (!z->right) {
Transplant(z, std::move(z->left));
} else {
auto y1 = z->right.get();
std::unique_ptr<BSTNode<T>> y;
if (!y1->left) {
y = std::move(z->right);
} else {
while (y1->left) {
y1 = y1->left.get();
}
y = std::move(Parent(y1)->left);
}
if (Parent(y.get()) != z) {
Transplant(y.get(), std::move(y->right));
y->right = std::move(z->right);
}
y->left = std::move(z->left);
Transplant(z, std::move(y));
}
}
相比之下,当我在子程序“Transplant”中删除右值引用时,它给出了段错误:
void Transplant(BSTNode<T>* u, std::unique_ptr<BSTNode<T>> v) { // crashes
auto p = Parent(u);
if (!p) {
root = std::move(v);
} else if (u == p->left.get()) {
p->left = std::move(v);
} else {
p->right = std::move(v);
}
}
崩溃发生在Delete
、Transplant(z, std::move(z->right));
的第18行。
这里有什么区别?有人告诉我 std::unique_ptr
通常按值传递。
此外,没有右值引用的Transplant
代码已经在BST代码中工作,即一个节点对其父节点有一个link。我不明白为什么。
当你按值传递一个unique_ptr
时,你失去了原来的指针:
void use_ptr(std::unique_ptr<int>) {}
std::unique_ptr<int> ptr = std::make_unique<int>(999);
use_ptr(std::move(ptr));
// here ptr no longer contains a pointer to 999
assert(!ptr);
std::cout << *ptr; // this is UB
当你通过右值引用传递它时,原始指针在函数调用后保持非空:
void use_ptr(std::unique_ptr<int>&&) {}
std::unique_ptr<int> ptr = std::make_unique<int>(999);
use_ptr(std::move(ptr));
// ptr still manages a pointer to `999`
assert(ptr);
std::cout << *ptr; // this is not UB
std::move
本身不会移动任何东西,它只是使某些东西成为 xvalue 的演员。当您从那个 xvalue 构造 std::unique_ptr<int>
参数时,实际的移动发生了——一旦您构造了一个新的 std::unique_ptr<int>
,您就失去了原来的那个。当你通过 &&
时,你不会构造任何东西,你只是传递一个引用。
考虑这个电话:
Transplant(z, std::move(z->right));
有
void Transplant(BSTNode<T>*, std::unique_ptr<BSTNode<T>>)
在 Transplant
里面,z->right
是一个 unique_ptr
没有任何东西,z->right.get() == nullptr
,因为你刚从它移到第二个 Transplant
参数。