Copy std:map of abstract classes?

Started by
6 comments, last by Sneftel 17 years, 10 months ago
I've got an std::map<int, AbstractClass*> that I need to make a copy of. I can't just copy the pointers, since I need to make copies of the actual objects so that if the original objects are altered or deleted, the copies will be retained as-is at the time of the copy. If I had an std::map<int, ConcreteClass*>, I could iterate through the map, making copies of each object pointed to, and insert these into the new map. Presumably this would need to be done by having a local pointer in the function set equal to a new ConcreteClass(), then assign the dereferenced pointer the value of the dereferenced pointer from the map, then insert the pointer into the map. But, since I have a map of pointers to an abstract class, I can't create a new AbstractClass object to insert into the map, nor can I dereference the pointers to the objects I want to make copies of... So (how) can I make a full copy of the map and its pointed-to contents? Thanks.
Advertisement
I'm not aware of any intrinsic way of doing this. I'd add a clone() function to the base class and override it in the derived classes.
If it is class you will ever want to serialize (ie: save to disk, send over network, etc) you could write the serialization code and call that for clones. Generally it involves an overloaded function to serialize and a factory method for deserialization.

(example)
Just to reiterate the core issue:

The core issue is that there is no fundamental method to copy polymorphic objects. It is always up to you to add whatever copy semantics you want to your program. One method is the virtual copy method (ie a clone or similar method), another method is the serialization method (it is just like the clone method only it writes into often writes into an intermediate format (that is meant to be persistable) and reads that back into a new object), another method would use a factory system to clone objects, but would probably be implemented internally just like the clone version).

But the main issue is not, where are how to make the calls. The main issue is figuring out what you want your clone / copy to MEAN (the semantics). For instance if your objects have internal pointers, is it a recursive deep copy? Are there any non-copyable items in your object? etc.

One you settle the semantics, implementation is really easy (as long as you don't find cases where your semantics can't work).
Just to demonstrate what a clone method is:

class Base{public:   virtual Base* clone() const = 0;   virtual ~Base() {}};class Foo : public Base{   // stuffpublic:   virtual Base* clone() const { return new Foo(*this); }};class Bar : public Base{   // stuffpublic:   virtual Base* clone() const { return new Bar(*this); }};int main(){   std::map<int, Base*> map1;   // fill it up   std::map<int, Base*> map2;   std::map<int, Base*>::iterator itor;   for(itor = map1.begin(); itor != map1.end(); ++itor)   {       int   key = itor->first;       Base* val = itor->second->clone()       map2.insert(std::make_pair(key, val));   }}
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Unfortunately, C++ copy-construction cannot be made "automatically virtual"; you can't just append 'virtual' to a cctor declaration the same way you would to a dtor or other member function and have it work. The problem is that the type of the copy-constructed thing gets determined by the (static) type of the variable being initialized, not the variable being copied.

To get around this, people normally use the "virtual clone idiom"; i.e. provide a virtual member function that does the virtual copying to create a heap-allocated object, and have it make use of the copy constructor to do so.

That said, you really don't want to do the iterating yourself. Since std::map can copy itself just fine as long as the keys and values copy themselves ok, what you want to do is make a value that "can copy itself ok" and also provide the necessary pointer interface. You can either make use of an existing smart pointer, such as those provided by Boost (recommended), or write a simple one yourself to handle the specific needs:

class AbstractHandle {  AbstractClass* ptr;  public:  // Unary operator* is the "dereference operator", which allows you to give  // pointer-like semantics to the class.  AbstractClass& operator*() { return *ptr; }  // And a similar overload so the -> operator will work. This one is a little  // trickier conceptually, but IIRC this is how it should be written:  AbstractClass* operator->() { return ptr; }  // And now all the wrapper RAII stuff.  // This constructor will "take ownership" of the pointed-to thing; the calling  // code should *not* delete the object. You would normally call this like:  // AbstractHandle x(new DerivedClass());  AbstractHandle(const AbstractClass* ptr): ptr(ptr) {}  // This version makes a copy of the passed in object and owns the copy. This  // works for a stack-allocated object:   // DerivedClass x;  // AbstractHandle y(x);  AbstractHandle(const AbstractClass& obj): ptr(obj.clone()) {}  // Copy construction. This facilitates the std::map copying.  AbstractHandle(const AbstractHandle& h): ptr(h->clone()) {}  // When you have copy construction, you normally need to write an   // assignment operator and destructor as well. This is no exception.  AbstractHandle& operator=(const AbstractHandle& other) {    // The copy-and-swap idiom shown here provides exception safety    // and implicitly ensures that self-assignment is correct.    AbstractHandle h(other);    std::swap(ptr, h.ptr);    // The AbstractClass that the current object used to point at will be    // destructed when 'h' falls out of scope.  }  ~AbstractHandle() { delete ptr; }};// Now you write your AbstractClasses using the appropriate idiom, and then// you can copy maps "directly", e.g.// std::map<int, AbstractHandle> tmp(original);
really nice post Zahlman, it even reminded me of the combination of things that could give more power than the sum of their parts (clone idiom + smart pointer = super power).
A slight modification to Fruny's code: clone methods are best when implemented with covariant return types.

class Base{public:   virtual Base* clone() const = 0;   virtual ~Base() {}};class Foo : public Base{   // stuffpublic:   virtual Foo* clone() const { return new Foo(*this); }};class Bar : public Base{   // stuffpublic:   virtual Bar* clone() const { return new Bar(*this); }};

This topic is closed to new replies.

Advertisement