Sign in to follow this  
Geoff the Medio

Copy std:map of abstract classes?

Recommended Posts

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.

Share this post


Link to post
Share on other sites
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)

Share this post


Link to post
Share on other sites
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).

Share this post


Link to post
Share on other sites
Just to demonstrate what a clone method is:

class Base
{
public:
virtual Base* clone() const = 0;
virtual ~Base() {}
};

class Foo : public Base
{
// stuff
public:
virtual Base* clone() const { return new Foo(*this); }
};

class Bar : public Base
{
// stuff
public:
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));
}
}

Share this post


Link to post
Share on other sites
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);

Share this post


Link to post
Share on other sites
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).

Share this post


Link to post
Share on other sites
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
{
// stuff
public:
virtual Foo* clone() const { return new Foo(*this); }
};

class Bar : public Base
{
// stuff
public:
virtual Bar* clone() const { return new Bar(*this); }
};

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this