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.

Morat
  • 503
  • 1
  • 5
  • 13
  • 1
    Why are you using raw pointers in the first place? – ildjarn Oct 27 '11 at 23:50
  • 2
    This entire approach invokes premonitions of a terrible, terrible car crash... – Kerrek SB Oct 27 '11 at 23:50
  • Why not `Boost.PointerContainers` ? – K-ballo Oct 28 '11 at 00:12
  • What does this have to do with sets? – Lightness Races in Orbit Oct 28 '11 at 03: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? – Björn Pollex Oct 28 '11 at 06:35
  • @BjörnPollex So what you are saying is move the relations out from the Node class to the Network class. Indeed, that seems to fix my faulty design; the downside is that I may be too far with the development to fix that easily – Morat Oct 28 '11 at 13:55
  • @K-ballo 2 reasons for that. 1: I was advised against using external dependencies. 2: My main language is not C++ and I may not be aware of all existent containers and classes. Forced to learn them as I go more or less. – Morat Oct 28 '11 at 13:58

3 Answers3

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.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • 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 Oct 28 '11 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) – Mooing Duck Oct 28 '11 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 Oct 28 '11 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.

Ayjay
  • 3,413
  • 15
  • 20
  • 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. – Mooing Duck Oct 28 '11 at 01: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 Oct 28 '11 at 01: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 Oct 28 '11 at 01: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_. – Mooing Duck Oct 28 '11 at 01: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 Oct 28 '11 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)

mskfisher
  • 3,291
  • 4
  • 35
  • 48
Petr M
  • 111
  • 3
  • 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. – Mooing Duck Oct 28 '11 at 01: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 Oct 28 '11 at 03: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. – Mooing Duck Oct 28 '11 at 03: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 Oct 28 '11 at 03:31