3

I am developing a project in C++ under Ubuntu 11.10 using the latest version of NetBeans. I'll only post minimal parts of the code relevant to the problem. Let's say I have the following code for a graph kind of problem:

typedef map<Node*, double, DereferenceCompare> Transitions;

class Node {
    int _nodeNumber;
    Transitions _transitions;
}

Each Node object holds a map of pointers to other Node objects. Now we have:

typedef set<Node*, DereferenceCompare> Nodes;

class Network {
    Nodes _network;
}

The problem: I am at a loss about writing a copy constructor for the class Network. What I am trying to achieve is to be able to do the following:

Network n1;
Network n2(n1);
//Have both n1 and n2 identical in structure but distinct in memory (deep copy).

Am I right in the following assumption: if I write a copy constructor for the Node class it would need to also copy the Transitions container. The Transitions container at that moment would hold pointers to the old nodes as the new ones do not exist yet.

This is my first post here. I hope I provided clear and sufficient information. I can further clarify if I was not coherent enough with my problem. Any help would be greatly appreciated.

7
  • 1
    Why are you using raw pointers in the first place?
    – ildjarn
    Commented Oct 27, 2011 at 23:50
  • 2
    This entire approach invokes premonitions of a terrible, terrible car crash...
    – Kerrek SB
    Commented Oct 27, 2011 at 23:50
  • Why not Boost.PointerContainers ?
    – K-ballo
    Commented Oct 28, 2011 at 0:12
  • What does this have to do with sets? Commented Oct 28, 2011 at 3:30
  • 2
    Can you change your design, so that the transitions for each node are stored in a mapping source-node-id->target-node-id in the network? Commented Oct 28, 2011 at 6:35

3 Answers 3

4

I've done this exact same thing before. It's tricky:

Network::Network(const Network& b) {
    //old to new mapping
    std::unordered_map<Node*, Node*> mapper(b._network.size()); 
    // duplicate all nodes without links
    for(auto iter = b.begin(); iter != b.end(); ++iter) {
        Node* new_node = new Node();
        try {
            _network.insert(new_node);
        } catch (std::bad_alloc& e) {
            delete new_node;
            throw;
        }
        mapper[iter->first] = _network; //and map the old Nodes to new ones
        new_node->_nodeNumber = iter->_node_number;
    }
    // THEN map the links
    for(auto iter = b.begin(); iter != b.end(); ++iter) {
        Node* new_node = mapper[iter->first];
        //for each link in the old one
        for(auto iter2 = iter->_transitions.begin(); 
                 iter2 != iter->_transitions.end();
                 ++iter2)
        {
            //link to the corresponding new node
            Node* connection = mapper[iter2->first];
            new_node->_transitions[connection ] = iter2->second;
        }
    }
}

[EDIT] Now exception safe
Also note I haven't attempted to validate the code in any way other than to compile. I just remember this is what I did years ago when I ran into the same problem.

3
  • Thank you to both Ayjay and Mooing Duck for their insight into this. I will have to accept Mooing Duck's post as the answer for my particular question. As for the solution to my faulty design I thank Bjorn Pollex for showing me a better way to design my Network class.
    – Morat
    Commented Oct 28, 2011 at 17:57
  • @Morat: BjornPollex's suggestion makes the copy easy and elegant, but means you cannot hand a single Node to a function that needs to know what it's connected to, you'd have to pass the whole network. (I'm not saying it's a bad idea, just that both choices have pros and cons) Commented Oct 28, 2011 at 18:02
  • Indeed, but I find that easy and elegant is better in the long run compared to what I have now. Will implement this for the moment and refactor it to Bjorn's solution when I find the time.
    – Morat
    Commented Oct 28, 2011 at 20:47
1

Given that you are using raw pointers for your key element, you can't use the default copy constructor but it is pretty simple to do yourself if your Node structure is a tree structure (ie. no cycles to worry about)

Network(const Network& other)
{
    if (this != &other)
    {
        for (auto it = other._network.begin(); it != other._network.end(); ++it)
        {
            _network.insert(new Node(*it));
        }
    }
}

Node(const Node& other)
{
    if (this != &other)
    {
        _nodeNumber = other._nodeNumber;
        for (auto it = other._transitions.begin(); it != other._transitions.end(); ++it)
        {
            _transitions[new Node(*it->first)] = it->second;
        }
    }
}

A much easier solution would be to store Node elements themselves so that the containers can manage the memory automatically, or use a smart pointer that represents the semantics you want.

If you are allowing cycles in the Node structure, that is really beyond the realm of copy constructors. You will want to write a seperate "copy" function that begins at some point and scans the entire structure, reproducing it along the way.

5
  • I wrote a copy constructor that handles cycles. Although I admit, moving it to a seperate function to avoid duplication for assignment is a good idea. Commented Oct 28, 2011 at 1:09
  • I really dislike the semantics of having one node's copy constructor copy an entire graph. If it is a tree structure, I don't mind as one node can be logically thought to "own" the nodes below it. Copying all sibling nodes stretches the semantics of a copy constructor much too far IMO.
    – Ayjay
    Commented Oct 28, 2011 at 1:27
  • But yes, I believe a proper answer could be either of these two answers, depending on whether the graph contains cycles or not.
    – Ayjay
    Commented Oct 28, 2011 at 1:28
  • Oh, I misread your sentance. Indeed, if there's cycles, that's beyond the copy constructor of the Node. It should part of the copy constructor of the Network. Commented Oct 28, 2011 at 1:46
  • Unfortunately I do need to allow cycles. The graph implementation is the basis for a Hidden Markov Model software which allows for cycles. I didn't know if there was an elegant and simple solution for this that I was missing. I will have to do something similar to what Mooing Duck suggested.
    – Morat
    Commented Oct 28, 2011 at 13:45
1

In your example n1 and n2 would point to the same instances of the Node(s). If one of them goes out of scope and deletes the Node(s) the other one would point to the freed memory.

If you use raw pointers in Nodes and Transitions, you must also provide assignment operators and destructors in addition to copy constructors for Network and Node classes.

Use smart pointers instead. In your case, if the “copy the pointer to object” is a copy semantic, using shared_ptr would save you from writing copy constructor, etc., manually - the default implementation would do the job. If the copy semantic is "copy object itself":

  • If performance is not an issue – store object in containers, use default copy c-tor etc

  • If performance important – write your own copy c-tor etc for both classes (or consider using third part or writing your own copy_ptr/clone_ptr)

4
  • 1
    I think he knows he has to deep copy pointers or he wouldn't have asked. Does unique_ptr do deep copies? If not, smart pointers are not related to his problem. Commented Oct 28, 2011 at 1:11
  • A unique_ptr object may be moved, but not copied. Depends on copy semantic shared_ptr or copy_ptr/clone_ptr could be used. Smart pointer could save from manual writing, maintaining and testing copy c-tor, d-tor, operator =
    – Petr M
    Commented Oct 28, 2011 at 3:13
  • He would need the nodes to have std::shared_ptrs to each other, clone_ptr in the networks, and even so, if the graph has cycles, the copy constructor will still look exactly the same. Commented Oct 28, 2011 at 3:24
  • From "STL Set of pointers. Copy constructor issue" perspective I would suggest to avoid writing copy c-tors etc by hand if it possible. Cycle graph implementation - it's another very interesting topic.
    – Petr M
    Commented Oct 28, 2011 at 3:31

Not the answer you're looking for? Browse other questions tagged or ask your own question.