views:

165

answers:

4

Hi, I was trying to solve a copy ctro qs given in lipman n not sure If i got it right. Since the class has pointers to itself it confused me a bit. Here is the code

#include <iostream>
#include <string>

using namespace std;

class BinStrTreeNode{

      public:
         BinStrTreeNode(const string& val):_val(val){ 
              _leftchild = new BinStrTreeNode("nup");
              _rightchild = new BinStrTreeNode("rup");
              cout << "ctor" << endl;
         };
         BinStrTreeNode(const BinStrTreeNode&);
     private:
         string _val;
         BinStrTreeNode *_leftchild;
         BinStrTreeNode *_rightchild;
};

BinStrTreeNode::BinStrTreeNode(const BinStrTreeNode& rhs):_val(rhs._val){
    cout << "copy ctor" << endl;
    _leftchild = new BinStrTreeNode(*(rhs._leftchild));
    _rightchild = new BinStrTreeNode(*(rhs._rightchild));
}

And it gives segementation fault. I know i need to define a dtor too but first I need to get this right. How should i define a copy ctor, assignment operator for a class like this?

Thanks

A: 

Your copy constructor is recursive. You might have exhausted memory or had new throw an out of memory exception or, most likely, dereferenced a null pointer in one of the copy constructor invocations.

bgbarcus
The other constructor has the same problem: recursive methods need base cases, otherwise they never stop running.
Nate Kohl
That's what's confusing me, so how do I define a copy ctor for a class that has pointers to itself?
tuco
A: 

Header Files

First, break your constructor declarations into a header file and then define them in a separate CPP file.

Copy Constructor Recursive Problem

Your copy constructor is recursive in that it keeps calling itself until the program runs out of available memory. This is bad.

Cloning (Copying) Tree

Based on the comments below, it seems you need to clone your tree. See example:

BinStrTreeNode* BinStrTreeNode::clone()
{
    if (BinStrTreeNode* tmp = new BinStrTreeNode)
    {
        tmp->value = value;
        if (leftchild) tmp->leftchild = leftchild->clone();
        if (rightchild) tmp->rightchild = rightchild->clone();
        return tmp;
    }
    return 0;
}

Tree Explanation

If I remember correctly from my college Computer Science days, I maintained a global copy of tree and "tagged" each node as either left or right. That way when I mapped it graphically in Qt, I knew how to display the tree.

You are going to have to maintain a left and right node for the tree also.

To get your code to work, I modified it as follows (ignore the TForm stuff, I just used a different IDE):

Unit1.Cpp
---------
#include "Unit1.h"
#include "File1.h"

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
    BinStrTreeNode *root;
    root = NULL;
}
//---------------------------------------------------------------------------

File1.cpp
---------
#include <iostream>
#include <string>
#include "File1.h"

    using namespace std;

    BinStrTreeNode::BinStrTreeNode(string val) {
           value = val;
           leftchild = NULL;
           rightchild = NULL;
           cout << "ctor" << endl;
    }

File1.h
-------
#include <iostream>
#include <string>

using namespace std;

class BinStrTreeNode{

            public:
                    BinStrTreeNode(string val);

            private:
                    string value;
                    BinStrTreeNode leftchild;
                    BinStrTreeNode rightchild;
};

A good example of a binary sort tree can be defined like this:

struct TreeNode {
         // An object of type TreeNode represents one node
              // in a binary tree of strings.

         string item;      // The data in this node.
         TreeNode left;    // Pointer to left subtree.
         TreeNode right;   // Pointer to right subtree.

         TreeNode(string str) {
                // Constructor.  Make a node containing str.
            item = str;
            left = NULL;
            right = NULL;
         }

};  // end struct Treenode

You then have to create the root and everything is added from there:

TreeNode *root;  // Pointer to the root node in the tree.
root = NULL;     // Start with an empty tree.

Then if you want to add an item to the binary sort tree, then you do something like this:

void treeInsert(TreeNode *&root, string newItem) {
              // Add the item to the binary sort tree to which the parameter
              // "root" refers.  Note that root is passed by reference since
              // its value can change in the case where the tree is empty.
          if ( root == NULL ) {
                 // The tree is empty.  Set root to point to a new node containing
                 // the new item.  This becomes the only node in the tree.
             root = new TreeNode( newItem );
                     // NOTE:  The left and right subtrees of root
                     // are automatically set to NULL by the constructor.
                     // This is important!
             return;
          }
          else if ( newItem < root->item ) {
             treeInsert( root->left, newItem );
          }
          else {
             treeInsert( root->right, newItem );
          }
}  // end treeInsert()
0A0D
Thanks but lets assume that instead of a tree it is just a normal class which has member pointers to itself. How do we define a copy ctor for such a class?
tuco
@tuco: You want the constructor to copy itself? When would it end? That may be the source of your problem with the seg fault. You are copying until you run out of memory.
0A0D
Ok so I should define a break condition in the copy ctor only then i can have a copy ctor for a class which has pointer members to itself? Or in the copy ctor i can initialize the pointer members to point to totally new objects.
tuco
@tuco: Well I think your problem is you are trying to create a copy constructor but you keep saying "new" when assigning the left and right child nodes. This will may cause a neverending copy condition. You want to just assign, not create a new constructor every time.
0A0D
@0A0D: So basically i can first allocate memory for leftchild and rightchild and should also define an assignment operator and then then simply assign objects pointed by leftchild, rightchild to objects pointed by leftchild, rightchild pointers of rhs. Does that make sense?
tuco
@tuco: The point of the copy constructor is to peform a deep copy. If you want a shallow copy, then the copy constructor is unnecessary. So in that case, just do `leftchild = rhs->leftchild`, etc.
0A0D
I'm not considering shallow copy. But too me it seems that doing a deep copy for a class which contains member pointers to itself is obviously not straight forward. Copying will be recursive and will need a break condition, probably if the leftchild, rightchild are null I can break out of recursion in this case.
tuco
@tuco: You need to clone your tree.. see edit
0A0D
@0A0D: That's great. thanks a lot.
tuco
`__fastcall`? `TForm`? `#pragma`? Why don't you just throw out the irrelevant stuff? Also `struct TreeNode` and `class BinStrTreeNode` don't contain pointers like you claim. Have you even tried compiling?
Georg Fritzsche
@Georg: Yep. And it works great.
0A0D
I don't think so - e.g. `BinStrTreeNode leftchild;` vs. `leftchild = NULL;` plus that a class can't have data-members of its own type.
Georg Fritzsche
@Georg: Works great on Borland C++ Builder 5.
0A0D
Couple of problems: Clone() is a Java concept not a C++ one as such it does not really help when the compiler tries to make copied. A side note: new never returns NULL so no point checking for it. Still don't see how you would handle cycle trees?
Martin York
@Martin: He wanted to copy his binary sort tree... I gave a solution that copies the members. That's as far as I went. If the question pertained to cycle trees then I would have included an example. This is not meant to be an exhaustive course on binary sort trees but rather why his copy constructor wasn't working. Whether you use the copy constructor concept or the clone() method, the idea is the same.
0A0D
I don't know more about BC5 then it being ancient and broken: http://codepad.org/Lg3QRhRh ... Use MingW, Cygwin, Visual Studio Express, Qt Developer, ... and drop `using namespace std;` from headers.
Georg Fritzsche
@Georg: Well it is the only compiler I have ATM, but then again its about trees and not about my compiler. Unless you are going to post another answer, you are just being a pain.
0A0D
Then use an online compiler like codepad or Comeau - insisting on errors and a broken compiler *after* having been shown that its wrong is a bit strange. This is simply invalid C++. Besides, Martin already posted a useful answer - why should i repeat that?
Georg Fritzsche
Consider this a virtual -1 as an SO bug doesn't let me vote on this answer.
Georg Fritzsche
+1  A: 

Your main problem is that both the constructors infinitely recurse:

  • Your constructor will recurse until you get a stack overflow, or until new throws a std::bad_alloc.
  • Your copy constructor will recurse until you get a stack overflow, or until it encounters an invalid leftchild or right child, when it will segfault.

Your underlying problem is that your recursive functions don't have a terminating condition. All functions, including constructors, need a code path that doesn't recurse.

Joe Gauterin
@joe:Yeah, I do get it now. I think putting a breaking condition of _leftchild, _rightchild being null might solve my problem here.
tuco
Yep. You'll also have to alter the other constructor to set leftchild and rightchild to null under some conditions.
Joe Gauterin
+1  A: 

you got it basically correct.
You just forgot to check for NULL pointers.

BinStrTreeNode::BinStrTreeNode(const BinStrTreeNode& rhs)
    :_val(rhs._val)
    ,_leftchild(NULL)
    ,_rightchild(NULL)
{
    cout << "copy ctor" << endl;
    if (rhs._leftchild  != NULL) {_leftchild  = new BinStrTreeNode(*(rhs._leftchild));}
    if (rhs._rightchild != NULL) {_rightchild = new BinStrTreeNode(*(rhs._rightchild));}
}

Note trees will not point to a node higher (or self) in the tree (as that would no longer be a tree it would be a graph). If your code is a graph then you need to re-write this so that you do a graph copy which is a lot more complex as you need to track which nodes have been copied in a map.

Though personally I would take it a step further and do everything in the initializer list:
Though I would just say that this is a personal preference.

BinStrTreeNode::BinStrTreeNode(const BinStrTreeNode& rhs)
    :_val(rhs._val)
    ,_leftchild ((rhs._leftchild  == NULL)?NULL:new BinStrTreeNode(*(rhs._leftchild)))
    ,_rightchild((rhs._rightchild == NULL)?NULL:new BinStrTreeNode(*(rhs._rightchild)))
{
    cout << "copy ctor" << endl;
}

Cycle trees (AKA Graphs)

Lets start from scratch here:
In this situation we need to track each node we create. Then when a pointer points back a node that has already been copied we use the copied node rather than creating a new one (otherwise we end up in a recursive nightmare following the loop round and around and around).

typedef std::map<BinStrTreeNode*,BinStrTreeNode*>   CopyMap;
BinStrTreeNode::BinStrTreeNode(const BinStrTreeNode& rhs)
    :_val(rhs._val)
{
    CopyMap    copyMap;
    copyMap[&rhs] = this;

    left  = copyGraph(left,copyMap);
    right = copyGraph(right,copyMap) 
}

private:
BinStrTreeNode* BinStrTreeNode::copyGraph(BinStrTreeNode* node,CopyMap& copyMap)
{
    if (node == NULL)
    {    return NULL;
    }

    if (copyMap[node] != NULL)
    {    return copyMap[ndoe];
    }

    return new BinStrTreeNode(*node, copyMap);
}
BinStrTreeNode::BinStrTreeNode(const BinStrTreeNode& rhs, CopyMap& copyMap)
    :_val(rhs._val)
{
    copyMap[&rhs] = this;
    left  = copyGraph(left,copyMap);
    right = copyGraph(right,copyMap) 
}
Martin York
Thanks a ton Martin !!
tuco