C++ 中 BST class 的五规则
Rule-of-five for a BST class in C++
我正在实现二叉搜索树 class 并且想知道我的 move/copy 构造函数和赋值运算符是否正确实现。 (它似乎工作正常,但这是我第一次实现这些构造函数和赋值运算符,恐怕我可能错过了一些东西。)
这是代码(也是in an online compiler):
编辑:这里是 updated code 基于@Alex Larionov 的评论:
#include <memory>
#include <iostream>
class BinarySearchTree {
public:
BinarySearchTree();
BinarySearchTree(int value);
BinarySearchTree(const BinarySearchTree& other_tree);
BinarySearchTree(BinarySearchTree&& other_tree);
BinarySearchTree& operator=(const BinarySearchTree& other_tree);
BinarySearchTree& operator=(BinarySearchTree&& other_tree);
~BinarySearchTree() = default;
void clear();
inline int size() const {
return tree_size;
}
inline bool empty() const {
return tree_size == 0;
}
private:
struct Node {
int val;
std::unique_ptr<Node> left = nullptr;
std::unique_ptr<Node> right = nullptr;
Node(const int value) :
val{value},
left{nullptr},
right{nullptr}
{}
};
std::unique_ptr<Node> root;
int tree_size;
void deep_copy_tree(std::unique_ptr<Node>& dest_node, const std::unique_ptr<Node>& source_node);
};
BinarySearchTree::BinarySearchTree() : root{nullptr}, tree_size{0} {
std::cout << "BinarySearchTree() constructor\n";
}
BinarySearchTree::BinarySearchTree(int value) : root{std::make_unique<Node>(value)}, tree_size{1} {
std::cout << "BinarySearchTree(int value) constructor\n";
}
BinarySearchTree::BinarySearchTree(const BinarySearchTree& other_tree) : root{nullptr}, tree_size{0} {
std::cout << "Copy constructor\n";
if (other_tree.tree_size == 0) return;
tree_size = other_tree.tree_size;
deep_copy_tree(root, other_tree.root);
}
BinarySearchTree::BinarySearchTree(BinarySearchTree&& other_tree) :
root(std::exchange(other_tree.root, nullptr)), tree_size(std::exchange(other_tree.tree_size, 0)) {
std::cout << "Move constructor\n";
}
BinarySearchTree& BinarySearchTree::operator=(const BinarySearchTree& other_tree) {
std::cout << "Copy assignment operator\n";
clear();
tree_size = other_tree.tree_size;
deep_copy_tree(root, other_tree.root);
return *this;
}
// EDIT: updated based on @Alex Larionov comment
BinarySearchTree& BinarySearchTree::operator=(BinarySearchTree&& other_tree) {
std::cout << "Move assignment operator\n";
clear();
tree_size = other_tree.tree_size;
other_tree.tree_size = 0;
root = std::move(other_tree.root);
return *this;
}
/*BinarySearchTree& BinarySearchTree::operator=(BinarySearchTree&& other_tree) {
std::cout << "Move assignment operator\n";
clear();
tree_size = other_tree.tree_size;
deep_copy_tree(root, other_tree.root);
other_tree.tree_size = 0;
other_tree.root = nullptr;
return *this;
}*/
void BinarySearchTree::clear() {
root = nullptr;
tree_size = 0;
}
void BinarySearchTree::deep_copy_tree(std::unique_ptr<Node>& dest_node, const std::unique_ptr<Node>& source_node) {
if (!source_node) return;
dest_node = std::make_unique<Node>(source_node->val);
deep_copy_tree(dest_node->left, source_node->left);
deep_copy_tree(dest_node->right, source_node->right);
}
int main()
{
BinarySearchTree myBST1(5);
BinarySearchTree myBST2 = myBST1; // copy constructor
BinarySearchTree myBST3(4);
myBST3 = myBST1; // copy assignment
std::cout << "myBST3.empty() before move: " << myBST3.empty() << '\n';
BinarySearchTree myBST4(std::move(myBST3)); // move constructor
std::cout << "myBST3.empty() after move: " << myBST3.empty() << '\n';
std::cout << "myBST4.empty() before move assignment: " << myBST4.empty() << '\n';
myBST2 = std::move(myBST4); // move assignment
std::cout << "myBST4.empty() after move assignment: " << myBST4.empty() << '\n';
return 0;
}
复制构造函数默认初始化,然后检查 other_tree
是否为空以避免深度复制它。但是您已经在 deep_copy_tree
中进行了检查。为什么不直接用它初始化?
BinarySearchTree::BinarySearchTree(const BinarySearchTree& other_tree) : tree_size{other_tree.tree_size} {
std::cout << "Copy constructor\n";
deep_copy_tree(root, other_tree.root);
}
为了更进一步,我会让 deep_copy_tree
return 而不是采用输出参数(同时删除“_tree”;它已经在 "Tree" class).
std::unique_ptr<Node> BinarySearchTree::deep_copy(const std::unique_ptr<Node>& source_node) {
if (!source_node) return nullptr;
auto dest_node = std::make_unique<Node>(source_node->val);
dest_node->left = deep_copy(source_node->left);
dest_node->right = deep_copy(source_node->right);
return dest_node;
}
这样你也可以在初始化列表中初始化 root
。
BinarySearchTree::BinarySearchTree(const BinarySearchTree& other_tree) : root(deep_copy_tree(other_tree.root)), tree_size{other_tree.tree_size} {
std::cout << "Copy constructor\n";
}
移动构造函数中,不需要使用std::exchange
。事实上,对于 root
,使用 std::move(other_tree.root)
也是一样的(从 unique_ptr
移出的是 nullptr
s)。
在复制赋值运算符中,您可能想要检查自赋值。
if (this != &other_tree)
您也不需要在任何一个赋值运算符中使用 clear
,因为对 unique_ptr
的赋值会有效地破坏它所持有的值。
我正在实现二叉搜索树 class 并且想知道我的 move/copy 构造函数和赋值运算符是否正确实现。 (它似乎工作正常,但这是我第一次实现这些构造函数和赋值运算符,恐怕我可能错过了一些东西。)
这是代码(也是in an online compiler): 编辑:这里是 updated code 基于@Alex Larionov 的评论:
#include <memory>
#include <iostream>
class BinarySearchTree {
public:
BinarySearchTree();
BinarySearchTree(int value);
BinarySearchTree(const BinarySearchTree& other_tree);
BinarySearchTree(BinarySearchTree&& other_tree);
BinarySearchTree& operator=(const BinarySearchTree& other_tree);
BinarySearchTree& operator=(BinarySearchTree&& other_tree);
~BinarySearchTree() = default;
void clear();
inline int size() const {
return tree_size;
}
inline bool empty() const {
return tree_size == 0;
}
private:
struct Node {
int val;
std::unique_ptr<Node> left = nullptr;
std::unique_ptr<Node> right = nullptr;
Node(const int value) :
val{value},
left{nullptr},
right{nullptr}
{}
};
std::unique_ptr<Node> root;
int tree_size;
void deep_copy_tree(std::unique_ptr<Node>& dest_node, const std::unique_ptr<Node>& source_node);
};
BinarySearchTree::BinarySearchTree() : root{nullptr}, tree_size{0} {
std::cout << "BinarySearchTree() constructor\n";
}
BinarySearchTree::BinarySearchTree(int value) : root{std::make_unique<Node>(value)}, tree_size{1} {
std::cout << "BinarySearchTree(int value) constructor\n";
}
BinarySearchTree::BinarySearchTree(const BinarySearchTree& other_tree) : root{nullptr}, tree_size{0} {
std::cout << "Copy constructor\n";
if (other_tree.tree_size == 0) return;
tree_size = other_tree.tree_size;
deep_copy_tree(root, other_tree.root);
}
BinarySearchTree::BinarySearchTree(BinarySearchTree&& other_tree) :
root(std::exchange(other_tree.root, nullptr)), tree_size(std::exchange(other_tree.tree_size, 0)) {
std::cout << "Move constructor\n";
}
BinarySearchTree& BinarySearchTree::operator=(const BinarySearchTree& other_tree) {
std::cout << "Copy assignment operator\n";
clear();
tree_size = other_tree.tree_size;
deep_copy_tree(root, other_tree.root);
return *this;
}
// EDIT: updated based on @Alex Larionov comment
BinarySearchTree& BinarySearchTree::operator=(BinarySearchTree&& other_tree) {
std::cout << "Move assignment operator\n";
clear();
tree_size = other_tree.tree_size;
other_tree.tree_size = 0;
root = std::move(other_tree.root);
return *this;
}
/*BinarySearchTree& BinarySearchTree::operator=(BinarySearchTree&& other_tree) {
std::cout << "Move assignment operator\n";
clear();
tree_size = other_tree.tree_size;
deep_copy_tree(root, other_tree.root);
other_tree.tree_size = 0;
other_tree.root = nullptr;
return *this;
}*/
void BinarySearchTree::clear() {
root = nullptr;
tree_size = 0;
}
void BinarySearchTree::deep_copy_tree(std::unique_ptr<Node>& dest_node, const std::unique_ptr<Node>& source_node) {
if (!source_node) return;
dest_node = std::make_unique<Node>(source_node->val);
deep_copy_tree(dest_node->left, source_node->left);
deep_copy_tree(dest_node->right, source_node->right);
}
int main()
{
BinarySearchTree myBST1(5);
BinarySearchTree myBST2 = myBST1; // copy constructor
BinarySearchTree myBST3(4);
myBST3 = myBST1; // copy assignment
std::cout << "myBST3.empty() before move: " << myBST3.empty() << '\n';
BinarySearchTree myBST4(std::move(myBST3)); // move constructor
std::cout << "myBST3.empty() after move: " << myBST3.empty() << '\n';
std::cout << "myBST4.empty() before move assignment: " << myBST4.empty() << '\n';
myBST2 = std::move(myBST4); // move assignment
std::cout << "myBST4.empty() after move assignment: " << myBST4.empty() << '\n';
return 0;
}
复制构造函数默认初始化,然后检查 other_tree
是否为空以避免深度复制它。但是您已经在 deep_copy_tree
中进行了检查。为什么不直接用它初始化?
BinarySearchTree::BinarySearchTree(const BinarySearchTree& other_tree) : tree_size{other_tree.tree_size} {
std::cout << "Copy constructor\n";
deep_copy_tree(root, other_tree.root);
}
为了更进一步,我会让 deep_copy_tree
return 而不是采用输出参数(同时删除“_tree”;它已经在 "Tree" class).
std::unique_ptr<Node> BinarySearchTree::deep_copy(const std::unique_ptr<Node>& source_node) {
if (!source_node) return nullptr;
auto dest_node = std::make_unique<Node>(source_node->val);
dest_node->left = deep_copy(source_node->left);
dest_node->right = deep_copy(source_node->right);
return dest_node;
}
这样你也可以在初始化列表中初始化 root
。
BinarySearchTree::BinarySearchTree(const BinarySearchTree& other_tree) : root(deep_copy_tree(other_tree.root)), tree_size{other_tree.tree_size} {
std::cout << "Copy constructor\n";
}
移动构造函数中,不需要使用std::exchange
。事实上,对于 root
,使用 std::move(other_tree.root)
也是一样的(从 unique_ptr
移出的是 nullptr
s)。
在复制赋值运算符中,您可能想要检查自赋值。
if (this != &other_tree)
您也不需要在任何一个赋值运算符中使用 clear
,因为对 unique_ptr
的赋值会有效地破坏它所持有的值。