views:

164

answers:

4

I've been trying to come up with a copy constructor for a tree. I've found quite a few suggestions.

This one interested me.

class TreeNode
{
    int ascii;
    TreeNode* left;
    TreeNode* right;

public:
    TreeNode() { ascii = 0; left = right = 0; }
    TreeNode* clone();
    // ...
};

 TreeNode* TreeNode::clone()
    {
        if (TreeNode* tmp = new TreeNode)
        {
            tmp->ascii = ascii;
            if (left) tmp->left = left->clone();
            if (right) tmp->right = right->clone();
            return tmp;
        }
        return 0;
    }

What does "if (TreeNode* tmp = new TreeNode) mean?

Other than that it looks alright. It just doesn't work very well.

Any idea what's wrong with it?

The example above came from this site.

A: 

It's been a while but the if ( ) is checking if the the allocation is non-null. IE the "new" succeeded.

Dan Vallejo
+3  A: 

I wouldn't call the method clone() a copy constructor. For example it is not a constructor in the first place just a method.

Implement a copy constructor in C++ like this (I left out all other members to keep it short):

class TreeNode {
    public:
       TreeNode(const TreeNode& source) {
          // copy members here, e.g.
          left = source.left;
          ...
       }
};

Edit: The example given implements/suggests a shallow copy. This is what the compiler creates for you if you didn't implement a copy constructor. So if you are happy with a shallow copy then you may as well leave out the copy constructor.

Of you prefer a deep copy this constructor may look as follows:

class TreeNode {
   public:
      TreeNode(const TreeNode& source) {
         left = source.left != NULL ? new TreeNode(*source.left) : NULL;
         ...
      }

By implementing the copy constructor you can also mix between deep-copy and shallow-copy if required.

John
Copy constructors in C++ should always deep-copy.
Daniel Trebbien
This is a bad copy-ctor, since it messes up the ownership (basically it's equivalent to what the compiler does for you). Having `clone()`, you should do `left = source.left->clone()`, similarly for `right`.
jpalecek
@Daniel: I think whether you want deep-copy depends on your requirements.@jpalecek: Yes, you are right. However, by overloading the copy constructor you can choose between shallow copy (what the compiler does) and deep copy (what you may want). Also you can choose between these options.@both: I'll update my answer to reflect your comments. Thanks for both of them!
John
Your distinction between "deep" and "shallow" copy doesn't really make sense in C++. A copy creates a logical copy of the object. End of story. There are no deep and shallow copies in C++, just correct and incorrect ones.
jalf
@jalf: I respect your answer. I disagree, though. Or maybe we are just using different definitions of the terms? Assume you have an object A1 and an object B. Object A1 has a pointer to B. Then you copy A1 to become A2. In that case would you copy B as well? Or would you just have both A1 and A2 point to the same B? Each choice has merits and meets a different set of requirements.
John
Additionally, this answer is an example of the wrong way to initialize members. Bjarne designed in the *ctor-initializer-list* for a reason.
Ben Voigt
@John: No, one choice is correct, the other isn't. C++ isn't garbage collected, so you have to track ownership yourself. Does A1 own the B object? If so, A2 must get its own copy. Is it reference-counted? Then A2 must get its own copy of the smart pointer. Is it a pointer to some static object? Then it's not a logical member of A1, and should not be copied.The point is that you don't have a choice. In Java you can choose "should I copy the reference or clone the object".In C++ you don't have the choice, because the object is designed so only one approach will work correctly
jalf
@jalf: I see your point and agree that ownership has to be considered. As you pointed out it depends on the circumstances how the pointer and the object it may point to needs to be treated. In some cases you copy and in some cases you don't. Looks as if we are on the same page.
John
+10  A: 

Well, for starters it's not a copy constructor - the copy constructors have a very well defined syntax in C++, so a proper copy constructor would have the prototype TreeNode(TreeNode const &). Just to get the terminology right (and the compiler will still generate a copy constructor as it has no idea what the clone() function is supposed to do).

The expression in the if statement will both allocate a new TreeNode object and purports to check that the allocation succeeded (by checking that the resulting pointer isn't 0). Unfortunately that's pre-standard C++ and modern C++ implementations that are standard conforming will throw a std::bad_alloc exception instead, so the test will mainly give the user a warm fuzzy feeling that something is being done about memory allocation failure, even if it isn't.

In order to make the code work as expected on a standard-compliant compiler, you'll have to use nothrow new. From memory the line would read something like this:

if (TreeNode* tmp = new(std::nothrow) TreeNode)

All that said, unless TreeNode is part of an object hierarchy that relies on the presence of the clone() function I would do away with it and implement a proper C++ constructor instead. That way, the compiler and you are on the same page when it comes to duplicating objects, plus other programmers will find it a little easier to follow your code.

Timo Geusch
A: 

What does if (TreeNode* tmp = new TreeNode) mean?

This is supposed to check the outcome of the allocation, ie. that it hasn't failed. However, this is a bad way of doing it, because:

  1. as others pointed out, new TreeNode will throw an exception in new C++ compilers
  2. Even if it hadn't thrown an exception, it is bad: when only some of the nodes fail to allocate, the caller of clone() won't have noticed anything, but will silently get only a part of the tree.
  3. when considering standard behaviour, this method is exception-unsafe.
jpalecek