I have implemented a simple binary search tree class in C++ using std::unique_ptr
objects to hold the pointers to each node. In doing this I have come across a situation that is somewhat questionable in the Delete
method. Since I can't copy the unique pointers, but I need a way to traverse the tree with a "temporary" pointer, I had to resort to using a raw pointer to do the traversal.
This is the section I am talking about (In the Delete
method):
// Need to use a raw pointer because I can't assign to a std::unique_ptr
Node* n = node.get();
while (n->right) {
// Using a reference to a unique_ptr instead of a raw pointer will cause this line to fail to compile
n = n->right.get();
}
Is this an acceptable thing to do?
It compiles and runs. And since the underlying pointer is still managed by a unique_ptr object it will be deleted properly. Everything about this works fine, but it just doesn't feel right for some reason.
The full source code is shown below (Feel free to comment on anything else that may be improved as well):
#include <iostream>
#include <memory>
using std::make_unique;
using std::unique_ptr;
template <typename T>
class Tree {
struct Node {
Node(const T& value)
: value(value), left(nullptr), right(nullptr) {}
T value;
unique_ptr<Node> left;
unique_ptr<Node> right;
};
public:
Tree() : root_(nullptr) {}
// Insert a value into the tree
void Insert(const T& value) {
Insert(root_, value);
}
// Delete a value from the tree
bool Delete(const T& value) {
return Delete(root_, value);
}
// Search the tree for a node and return true if it is found
bool Contains(const T& value) const {
return Contains(root_, value);
}
private:
void Insert(unique_ptr<Node>& node, const T& value) {
if (not node) {
node = make_unique<Node>(value);
}
else {
value < node->value
? Insert(node->left, value)
: Insert(node->right, value);
}
}
bool Delete(unique_ptr<Node>& node, const T& value) {
if (not node) {
return false;
}
else if (value == node->value) {
if (node->left) {
unique_ptr<Node>& right = node->right;
node = move(node->left);
if (right) {
// Need to use a raw pointer because I can't assign to a std::unique_ptr
Node* n = node.get();
while (n->right) {
// Using a reference to a unique_ptr instead of a raw pointer will cause this line to fail to compile
n = n->right.get();
}
n->right = move(right);
}
}
else {
node = move(node->right);
}
return true;
}
else {
return value < node->value
? Delete(node->left, value)
: Delete(node->right, value);
}
}
bool Contains(const unique_ptr<Node>& node, const T& value) const {
if (not node) {
return false;
}
else if (node->value == value) {
return true;
}
else {
return value < node->value
? Contains(node->left, value)
: Contains(node->right, value);
}
}
unique_ptr<Node> root_;
};
std::shared_ptr
? \$\endgroup\$std::shared_ptr
, but usingstd::unique_ptr
is generally more efficient (theres no extra memory overhead and move operations are faster than copy). I guess using a raw pointer to refer to something owned by astd::unique_ptr
is essentially the same as creating astd::weak_ptr
from astd::shared_ptr
? \$\endgroup\$