5

I am trying to implement BST with unique_ptr. I got a working program for shared_ptr. How do I go about using unique_ptr instead to enforce the single ownership semantics of the BinarySearchTree?

When I replace shared_ptr with unique_ptr, I get compilation errors beyond my understanding.

#include <iostream>
#include <memory>

template<class T>
class BinarySearchTree{
    struct TreeNode;
    typedef std::shared_ptr<TreeNode> spTreeNode;
    struct TreeNode{
        T data;
        spTreeNode  left;
        spTreeNode  right;
        TreeNode(const T & value):data(value),left(nullptr),right(nullptr){}
    };



    spTreeNode root;
    bool insert(spTreeNode node);
    void print(const spTreeNode) const ;
public:
    BinarySearchTree();
    void insert( const T & node);
    void print()const;
};

template<class T>
BinarySearchTree<T>::BinarySearchTree():root(nullptr){}

template<class T>
void BinarySearchTree<T>::insert(const T & ref)
{
    TreeNode *node = new TreeNode(ref);
    if (root==nullptr)
    {
        root.reset(node);
    }
    else
    {
        spTreeNode temp = root;
        spTreeNode prev = root;
        while (temp)
        {
            prev = temp;
            if (temp->data < ref)
                temp = temp->right;
            else
                temp = temp->left;
        }
        if (prev->data  < ref)
            prev->right.reset(node);
        else
            prev->left.reset(node);
    }
}

template<class T>
void BinarySearchTree<T>::print()const
{
    print(root);
}

template<class T>
void BinarySearchTree<T>::print(const spTreeNode node)const
{
    if (node==nullptr)
        return;
    print(node->left);
    std::cout << node->data<< std::endl;
    print(node->right);
}

int main()
{
    BinarySearchTree<int> bst;
    bst.insert(13);
    bst.insert(3);
    bst.insert(5);
    bst.insert(31);
    bst.print();
    return 0;
}

EDIT: Compilation errors in case anyone is interested. Warning: Wall of text.

    prog.cpp: In instantiation of ‘void BinarySearchTree<T>::insert(const T&) [with T = int]’:
prog.cpp:75:18:   required from here
prog.cpp:39:27: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = BinarySearchTree<int>::TreeNode; _Dp = std::default_delete<BinarySearchTree<int>::TreeNode>]’
         spTreeNode temp = root;
                           ^
In file included from /usr/include/c++/4.8/memory:81:0,
                 from prog.cpp:2:
/usr/include/c++/4.8/bits/unique_ptr.h:273:7: error: declared here
       unique_ptr(const unique_ptr&) = delete;
       ^
prog.cpp:40:27: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = BinarySearchTree<int>::TreeNode; _Dp = std::default_delete<BinarySearchTree<int>::TreeNode>]’
         spTreeNode prev = root;
                           ^
In file included from /usr/include/c++/4.8/memory:81:0,
                 from prog.cpp:2:
/usr/include/c++/4.8/bits/unique_ptr.h:273:7: error: declared here
       unique_ptr(const unique_ptr&) = delete;
       ^
prog.cpp:43:18: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>& std::unique_ptr<_Tp, _Dp>::operator=(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = BinarySearchTree<int>::TreeNode; _Dp = std::default_delete<BinarySearchTree<int>::TreeNode>]’
             prev = temp;
                  ^
In file included from /usr/include/c++/4.8/memory:81:0,
                 from prog.cpp:2:
/usr/include/c++/4.8/bits/unique_ptr.h:274:19: error: declared here
       unique_ptr& operator=(const unique_ptr&) = delete;
                   ^
prog.cpp:45:22: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>& std::unique_ptr<_Tp, _Dp>::operator=(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = BinarySearchTree<int>::TreeNode; _Dp = std::default_delete<BinarySearchTree<int>::TreeNode>]’
                 temp = temp->right;
                      ^
In file included from /usr/include/c++/4.8/memory:81:0,
                 from prog.cpp:2:
/usr/include/c++/4.8/bits/unique_ptr.h:274:19: error: declared here
       unique_ptr& operator=(const unique_ptr&) = delete;
                   ^
prog.cpp:47:22: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>& std::unique_ptr<_Tp, _Dp>::operator=(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = BinarySearchTree<int>::TreeNode; _Dp = std::default_delete<BinarySearchTree<int>::TreeNode>]’
                 temp = temp->left;
                      ^
In file included from /usr/include/c++/4.8/memory:81:0,
                 from prog.cpp:2:
/usr/include/c++/4.8/bits/unique_ptr.h:274:19: error: declared here
       unique_ptr& operator=(const unique_ptr&) = delete;
                   ^
prog.cpp: In instantiation of ‘void BinarySearchTree<T>::print() const [with T = int]’:
prog.cpp:79:15:   required from here
prog.cpp:59:15: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = BinarySearchTree<int>::TreeNode; _Dp = std::default_delete<BinarySearchTree<int>::TreeNode>]’
     print(root);
               ^
In file included from /usr/include/c++/4.8/memory:81:0,
                 from prog.cpp:2:
/usr/include/c++/4.8/bits/unique_ptr.h:273:7: error: declared here
       unique_ptr(const unique_ptr&) = delete;
       ^
prog.cpp:63:6: error:   initializing argument 1 of ‘void BinarySearchTree<T>::print(BinarySearchTree<T>::spTreeNode) const [with T = int; BinarySearchTree<T>::spTreeNode = std::unique_ptr<BinarySearchTree<int>::TreeNode, std::default_delete<BinarySearchTree<int>::TreeNode> >]’
 void BinarySearchTree<T>::print(const spTreeNode node)const
          ^
Ian McGrath
  • 982
  • 2
  • 10
  • 23
  • Maybe posting these compilation errors would help you by means of _our_ understanding. Edit : however one of your functions takes a pointer by value (i.e copy). But `unique_ptr`s can only be moved. – Quentin Jul 06 '14 at 00:28
  • @Quentin, I can but they are very long. Anyway, I will post them – Ian McGrath Jul 06 '14 at 00:29
  • When traversing the tree, use `TreeNode*` (no ownership), not `unique_ptr` (exclusive ownership). The tree, not the code traversing it, has ownership. – Ben Voigt Jul 06 '14 at 00:47

2 Answers2

14

unique_ptrs are not assignable but moveable. I reworked your example and now works with unique_ptrs. Notice, that I use std::move in order to move contents from one unique_ptr to another. Also due to the fact that unique_ptr isn't copyable, I pass unique_ptrs in member functions by reference and not by value:

#include <iostream>
#include <memory>

template<class T>
class BinarySearchTree{
    struct TreeNode;
    typedef std::unique_ptr<TreeNode> spTreeNode;
    struct TreeNode{
        T data;
        spTreeNode  left;
        spTreeNode  right;
        TreeNode(const T & value):data(value),left(nullptr),right(nullptr){}
    };



    spTreeNode root;
    bool insert(spTreeNode &node);
    void print(const spTreeNode&) const ;
public:
    BinarySearchTree();
    void insert( const T & node);
    void print()const;
};

template<class T>
BinarySearchTree<T>::BinarySearchTree():root(nullptr){}

template<class T>
void BinarySearchTree<T>::insert(const T & ref)
{
    std::unique_ptr<TreeNode> node(new TreeNode(ref));
    if (root == nullptr) {
        root = std::move(node);
    } else {
        TreeNode* temp = root.get();
        TreeNode* prev = root.get();
        while (temp != nullptr) {
            prev = temp;
            if (temp->data < ref)
                temp = temp->right.get();
            else
                temp = temp->left.get();
        }
        if (prev->data < ref)
            prev->right = std::move(node);
        else
            prev->left = std::move(node);
    }
}

template<class T>
void BinarySearchTree<T>::print()const
{
    print(root);
}

template<class T>
void BinarySearchTree<T>::print(const std::unique_ptr<TreeNode> &node) const
{
    if(node == nullptr) return;
    print(node->left);
    std::cout << node->data<< std::endl;
    print(node->right);
}

int main()
{
    BinarySearchTree<int> bst;
    bst.insert(13);
    bst.insert(3);
    bst.insert(5);
    bst.insert(31);
    bst.print();
    return 0;
}

LIVE DEMO

101010
  • 41,839
  • 11
  • 94
  • 168
  • so basically, when you need to "assign" -- use std::move and when you need to just traverse, use get()? – Ian McGrath Jul 06 '14 at 00:54
  • @IanMcGrath basically yes, note however that `unique_ptr` isn't also copyable. Thus, you can't pass it to function as input argument by value, but rather by reference. Also mind, that with `get` you access the contents of the `unique_ptr` make sure not to assign this contents to another smart-pointer. – 101010 Jul 06 '14 at 00:58
  • Thanks. Last question. If I have to return a node from some function, say getMaxValueNode(), how should I return? – Ian McGrath Jul 06 '14 at 01:00
  • @IanMcGrath IMHO you should return the node itself not the `unique_ptr` since you need to access this node. As mentioned previously `unique_ptr`s are not copyable thus you can't return a `unique_ptr` that already exists. Mind however that if you construct a `unique_ptr` in the body of a function and then in the same function return it the compiler is smart enough to move this `unique_ptr` so in this case returning a `unique_ptr` is allowable. – 101010 Jul 06 '14 at 01:05
  • Is there a way to do this without `get()`? It is troublesome that we go thru all this work to use `unique_ptr` and then switch to a raw ptr while we traverse the tree. I see why you do it, and I don't know how to avoid it. Something tells me the Scott Meyers would disapprove. – wcochran May 09 '17 at 22:38
3

Template-flavored errors always tend to be horrible, but don't panic ! All these "use of deleted function" are about you trying to copy a unique_ptr, which gets its superpowers from being movable-only. Jump to each of these lines, and analyze the situation :

Do you wish to transfer ownership of the pointee ? Then pass the unique pointer by rvalue reference and move it to its new holder.

// Take unique_ptr by rvalue reference
void TreeNode::setLeft(std::unique_ptr<TreeNode> &&node) {
    // Move it in a member variable
    left = std::move(node);
    // Now it's ours !
}

Do you just wish to refer to the pointee ? Use a const lvalue reference to the unique_ptr, or pass a const raw pointer from unique_ptr.get().

// Take raw const pointer
bool isNodeLeft(TreeNode const *node) const {
    // Look at it but don't disturb it
    return node->value <= value;
}

Once all of these are out of the way, you'll either have a compiling code, some other error you'll resolve, or I'll update this answer.

Note : that TreeNode *node = new TreeNode(ref); is howling about exception safety. You should use make_unique (from C++1y, or craft your own).

Here it is :

template <class T, class... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
    return unique_ptr<T>(new T(std::forward<Args>(args)...));
}
Quentin
  • 62,093
  • 7
  • 131
  • 191
  • I am sorry, I do not understand. Really new to C++11 smart pointers. Can you please post one or two lines of code? Thank you very much. – Ian McGrath Jul 06 '14 at 00:44
  • figured it out. Thanks for the pointers (pun intended). Now, all I do not understand is the make_unique thing, can you please post a line of code to make me understand what you meant? – Ian McGrath Jul 06 '14 at 00:48
  • @IanMcGrath here they are ! – Quentin Jul 06 '14 at 00:58