views:

72

answers:

2

In the trivial example inheritance hierarchy:

class Food
{
    virtual ~Food();
};

class Fruit : public Food
{
    virtual ~Fruit();
};

class Apple: public Fruit
{
    virtual ~Apple();
}

class Vegetable: public Food
{
    virtual ~Vegetable();
}

I wish to create a method that can clone an object from its subclass or baseclass instance:

Apple* apple1 = new Apple();
Apple* clone1 = apple1->clone();

Food* food1 = apple1;
Apple* clone2 = food1->clone();

I see a few possible solutions to the problem:

  • Use polymorphism to create a virtual function that is explicitly defined for each subclass.

  • Use a templated factory method to take an instance of a class and the type of the subclass to create the new instance.

  • Register an id with each class and subclass that is stored elsewhere to lookup which subclass to create on calling a baseclass clone function.

None of these seem ideal, but I'm leaning more towards the third solution as it simplifies calling the clone function without requiring a definition to be written for every subclasses (for which there will be a lot).

However, I'm very much open to any suggestions, are there better ways of doing this?

A: 

Hi.

Your last example, ...

Food* food1 = dynamic_cast<Food*>(apple1);
Apple* clone2 = f1->clone();

... won't work, even with the speling eror corrected. You need the cast the other way:

Food* food1 = apple1;
Apple* clone2 = dynamic_cast<Apple*>( f1->clone() );

Apart from that, the practical solution to cloning in C++ is to define a macro:

#define YOURPREFIX_IMPLEMENT_CLONING( Class )                       \
    virtual Class*                                                  \
        virtualCloneThatIsUnsafeToCallDirectly() const              \
    {                                                               \
        assert( typeid( *this ) == typeid( Class ) );               \
        return new Class( *this );                                  \
    }                                                               \
                                                                    \
    OwnershipPtr< Class >                                           \
        clone() const                                               \
    {                                                               \
        return OwnershipPtr< Class >(                               \
            virtualCloneThatIsUnsafeToCallDirectly()                \
            );                                                      \
    }

... where OwnershipPtr might be e.g. std::auto_ptr.

Then all you have to do is to place a macro invocation in each class that should support cloning. Simple.

It's also possible to implement reusable cloning via templating, but that's more complicated both for the implementation and the usage. You can read about that in my blog posting "3 ways to mix in a generic cloning implementation". The conclusion is, however, that the macro is most practical. :-)

Cheers & hth.,

Alf P. Steinbach
No, the practical solution is most assuredly NOT a macro.
DeadMG
Alf P. Steinbach
@DeadMG, PS, if you're unable to come up with something more practical, please remove the downvote. TIA.,
Alf P. Steinbach
@Alf: I was just posting my own solution. In retrospect, I'm not entirely certain that a downvote was the right choice. However, I can't remove it and not upvote you. Also, for some reason, I can't reconsider unless you edit. That never happened before.
DeadMG
@Alf: If you edit your post (preferably with something trivial), then I can undo my downvote.
DeadMG
+2  A: 

You can use the CRTP to automatically implement a Clone method.

template<typename T, typename Derive> class CloneImpl : public Derive {
public:
    virtual Derive* clone() {
        return new T(static_cast<const T&>(*this));
    }
};
class Food {
public:
    virtual Food* clone() = 0;
    virtual ~Food() {}
};
class Fruit : public Food {
};
class Dairy : public Food {
};
class Apple : public CloneImpl<Apple, Fruit> {
};
class Banana : public CloneImpl<Banana, Fruit> {
};
class Cheese : public CloneImpl<Cheese, Dairy> {
};
class Milk : public CloneImpl<Milk, Dairy> {
};

In this case, you can always call Clone() to copy the current object with a fresh allocation on the heap and you don't need to implement it again in any class. Of course, if your Clone semantics need to be different, then you can just alter the function.

Not only can the CRTP implement clone() for you, it can even do it between different inheritance hierarchies.

DeadMG
@DeadMG: your solution doesn't seem to work with the OP's class hierarchy. Could you show how you address the issue of that hierarchy, Food <- Fruit <- Apple, so that all classes are clonable?
Alf P. Steinbach
@Alf: You are correct in that I missed the Fruit intermediary class, so I shall edit.
DeadMG
@DeadMG: in the OP's example those are (apparently) concrete classes. Now they're abstract. A more concrete in-practice example of a hierarchy concrete classes where cloning is needed (in C++98) is an exception class hierarchy. So, could you perhaps also make the classes concrete? So that all the classes are clonable? Cheers,
Alf P. Steinbach
@Alf: Edited my answer with a superior solution involving non-abstract classes.
DeadMG
@Steve Jessop: My edit containing that step was posted three minutes before your comment. Long think time? :P
DeadMG
@DeadMG: Nope, just reading the other answers. Javascript "magic" can load the new comments, but apparently not the edit to your answer. So I posted my comment, saw your comment, spent a while wondering what edit you meant, hit refresh, understood, deleted my comment, by which time you'd had time to see my comment and (rightly) object...
Steve Jessop
Alf P. Steinbach
@DeadMG: C++98 constructor arg forwarding is not that difficult really. E.g. one implementation at my blog (including arg forwarding version of your solution above). But it adds to the complexity. And dependencies. Now, how about that downvote?
Alf P. Steinbach
@Alf: Unless you want to implement virtual templates, that's never gonna happen, because every set of arguments you want to support will have to be explicitly provided in the base, effectively removing any real ability to seamlessly pass arguments. Either that, or you go down the strange kind of intrusive dynamic arguments with void*s and casts and such, which is a separate question as it's not the same effect. Check my last comment on your post for the downvote thing.
DeadMG
Alf P. Steinbach
@Alf: It's not about C++98 forwarding. C++0x doesn't introduce this forwarding either. You can't forward *virtually*, only statically, in C++0x, because virtual functions can't be templated. Static forwarding is another thing.
DeadMG
@DeadMG: sorry, I don't understand what you mean (and keep in mind, I've posted a working implementation of your scheme, with forwarding, and that was long ago and I doubt I was the first). Forwarding is needed because a base class in the hierarchy may require constructor arguments. Cheers,
Alf P. Steinbach
@Alf: That's an entirely different thing. The entire point of clone is to copy, using the derived class's copy constructor. If the derived class cannot construct the base class from it's copy constructor, then that derived class cannot implement clone. The base class doesn't even come into the equation. Clone is about copying a class- not constructing the base class.
DeadMG
Alf P. Steinbach
@DeadMG: e.g., try to apply the scheme to `struct Base { Base( int ) {} };` and `struct Derived: Base { Derived( int x ): Base( x ) {} };`.
Alf P. Steinbach
@Alf: OIC wut you mean thar- the copy constructor isn't broken, but the regular ones are. I can work around it, but agree - it is a problem of this specific implementation.
DeadMG
@DeadMG: he he, at last we're talking about the same thing! :-) Well, the regular constructors aren't broken in that example, they just don't support default construction, or for the usage in question one doesn't want default construction. The only remedy I know is argument forwarding. It can be general or it can be reimplemented on a case by case basis (but if the latter, it's just as easy or easier to just implement `clone` directly in each class). With C++0x a general forwarding implementation of your scheme will be "easier". It can be done in C++98 (as in my blog), but not perfect. Cheers,
Alf P. Steinbach