A binary tree , its copy constructor and assignment operator
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;
}