A binary tree , its copy constructor and assignment operator I implemented a binary tree in the following code. Its node's copy constructor and assignment operator should copy itself and all its descendents. Similarity a node's destructor should delete itself and all nodes descended from it.The print function just prints each node in a new line. How can I pretty-print it? Please point to errors(if any) and suggestions. 

#include &lt;iostream&gt;

struct Node
{
    Node(int);
    Node(const Node&amp;);
    Node&amp; operator=(const Node&amp;);
    ~Node();

    Node* left;
    Node* right;
    int value;
};

Node::Node(int v)
:left(nullptr),
right(nullptr),
value(v)
{}

Node::Node(const Node&amp; other)
:left(nullptr),
 right(nullptr),
 value(other.value)
{
    if (other.left != nullptr )
    {
        left = new Node(*other.left);
    }
    if (other.right != nullptr)
    {
        right = new Node(*other.right);
    }
}

Node&amp; Node::operator=(const Node&amp; other)
{
    value = other.value;

    Node * left_orig = left;
    left = new Node(*other.left);
    delete left_orig;

    Node * right_orig = right;
    right = new Node(*other.right);
    delete right_orig;

    return *this;
}

Node::~Node()
{
    if (left != nullptr )
    {
        delete left;
    }
    if (right != nullptr)
    {
        delete right;
    }
}

Node* make_copy(Node* other)
{
    Node * new_node = new Node(*other); // copy constructor invoked
    return new_node;
}


void print(Node* n)
{
    if (n == nullptr)
    {
        return;
    }
    std::cout &lt;&lt; n &lt;&lt; std::endl;
    print(n-&gt;left);
    print(n-&gt;right);

}


int main()
{
    Node* n = new Node(1);

    n-&gt;left = new Node(2);
    n-&gt;right = new Node(3);

    n-&gt;left-&gt;left = new Node(4);
    n-&gt;left-&gt;right = new Node(5);

    n-&gt;right-&gt;left = new Node(6);
    n-&gt;right-&gt;right = new Node(7);

    print(n);
    auto nc =  make_copy(n);
    print(nc);
}
Assignment operator.

The assignment operator is not 100% exception safe.
You should not modify the current object while you have not yet finished making the copy.

Node&amp; Node::operator=(const Node&amp; other)
{
    value = other.value;

    Node * left_orig = left;
    left = new Node(*other.left);
    delete left_orig;

    // You have modified the left side.
    // If while makeing the copy of the right side
    // it throws an exception (that is caught). Then you
    // have an object that is half one thing (new stuff)
    // and half the other (old stuff on the right).
    Node * right_orig = right;
    right = new Node(*other.right);
    delete right_orig;

    return *this;
}


It should look more like this:

Node&amp; Node::operator=(const Node&amp; other)
{
    Note* newleft  = nullptr;
    Node* newright = nullptr;

    // Do the dangerous stuff in isolation.
    // without changing the chaging the current object.
    try {
        newleft  = new Node(*other.left);
        newright = new Node(*other.right);
    }
    catch(...) {
        // If there was a problem
        // clean up any temporary objects.
        delete newleft;
        delete newright;
        // Then re-throw
        throw;
    }
    // Now perform the exception safe change of state.
    // None of these operations are allowed to throw.
    value = other.value;
    std::swap(left,  newLeft);
    std::swap(right, newRight);

    // Now that the object is in a consistent state.
    // we can delete the old data.
    // Do this last (in other types where the data is not in
    // this can potentially throw).
    delete newLeft;
    delete newRight;

    return *this;
}


Now that looks like a lot of hard work.
There is an easier way to achieve exactly the same affect. You can use the copy and swap idiom.

Node&amp; Node::operator=(Node other)    // Pass by value to generate a copy.
{
    other.swap(*this);               // Swap the state of this and the
                                     // copy we created in `other`
    return *this;
}                                    // destructor of other now
                                     // does the tidy up.


Destructor

Calling delete on a nullptr is valid and does nothing.

Node::~Node()
{
    if (left != nullptr )
    {
        delete left;
    }
    if (right != nullptr)
    {
        delete right;
    }
}


So we can simplify this too.

Node::~Node()
{
    delete left;
    delete right;
}


Why do you need a make_copy?

Node* make_copy(Node* other)
{
    Node * new_node = new Node(*other); // copy constructor invoked
    return new_node;
}


It is quite normal to call the copy constructor directly.

Print statement

You can make the print function a method. Also to make it more versatile you should pass a stream to which you want to print (so it can also print to file). If you want a no argument version just make the stream parameter default to std::cout.

void Node::print(std::iostream&amp; str = std::cin)
{
    std::cout &lt;&lt; value &lt;&lt; std::endl;
    if (left) {
        left-&gt;print(str);
    }
    if (right) {
        right-&gt;print(str);
    }
}


This makes it easy to define the output operator for your class.

std::ostream&amp; operator&lt;&lt;(std::ostream&amp; str, Node&amp; data)
{
    data.print(str);
    return *this;
}