3

I have a member pointer to an abstract class, and I ran into the common problem where I can't copy what the pointer is pointing to because I don't know what derived class the pointer is pointing to.

When Googling for a solution, I found this question: Copy constructor: deep copying an abstract class

It brings up this exact problem and the answers present the virtual constructor idiom, which involves that you add a purely virtual clone() method to the abstract base class, that you then override in all the derived classes, so that they return allocated pointers to their actual types.

The problem is that this solution doesn't work for me, since the abstract type that I need to copy is from a library (SFML). This means that I can't alter the class to add the clone() method.

How do I solve the problem without altering the abstract base class?

JFMR
  • 23,265
  • 4
  • 52
  • 76
JensB
  • 839
  • 4
  • 19
  • 4
    If the library does not provide a solution, then there is none. If the library is reasonably well constructed and does not allow for cloning, then cloning is not something that is necessary. – sp2danny Jan 05 '21 at 22:55
  • 1
    Can you just make a new (non-abstract) class that 'wraps' that from the SFML library. You can implement the suggested `clone` method in that, then derive all your own classes use the SFML *via* that new intermediary. – Adrian Mole Jan 05 '21 at 22:55
  • If you have no code to copy the object, then you can't copy the object unless you write code to copy the object. That code has to know how to copy the object. If you don't know how to copy the object and don't already have code to copy the object, you're stuck. The object may not even be copyable -- for example, it makes no sense to copy an object that contains a network connection or exclusive access to any other hardware resource. This one might, or something derived from it later might. – David Schwartz Jan 05 '21 at 23:03
  • @AdrianMole I don't really understand what you mean. Do you think you can write some example code and post it as an answer? Would be really helpful – JensB Jan 05 '21 at 23:03
  • @JensB He's saying write a method to make a copy of the object and in that method, just create a new instance of the SFML object for the copy to have. – David Schwartz Jan 05 '21 at 23:04
  • @DavidSchwartz Not quite. I meant, rather than deriving objects from the SFML class, derive them from an intermediate class, itself derived from the relevant SFML base. I can't really give an example because: (a) I don't know how SFML works; and (b) I don't know how the OP is using its abstract classes to get their own. – Adrian Mole Jan 05 '21 at 23:07
  • @JensB Do you know the object's actual type at any point in time (i.e., is that an object you constructed yourself or returned from a function with a documented actual return type)? If you don't then there's no way. – user202729 Jan 06 '21 at 00:40

1 Answers1

2

For the sake of this answer, let's assume you are working with the SFML's sf::Shape abstract class:

sf::Shape hierarchy

So, you are probably dealing with sf::CircleShape, sf::ConvexShape, and sf::RectangleShape objects through sf::Shape pointers or references. These are concrete classes.

Instead of working through sf::Shape you could define the following abstract class ClonableShape:

struct ClonableShape {
    virtual ~ClonableShape() = default;

    virtual std::unique_ptr<ClonableShape> clone() const = 0;

    // extend at will with the member functions from sf::Shape, e.g.:
    virtual const sf::Vector2f& getPosition() const = 0;
    // ...
};

You can use this interface to clone those objects polymorphically by insantiating the following class template with the concrete classes above:

template<typename T>
struct Clonable: T, ClonableShape {
    Clonable() = default;

    template<typename... Args>
    Clonable(Args... args): T(args...) {}

    std::unique_ptr<ClonableShape> clone() const override {
        return std::make_unique<Clonable<T>>(*this);
    }

    const sf::Vector2f& getPosition() const override {
        // careful not to call ClonableShape::getPosition() instead
        return T::getPosition();
    }   
};

That is, this solution relies on the inheritance but you may want to consider composition insteadX.

Finally, you can clone the shape objects polymorphically:

std::unique_ptr<ClonableShape> shapeA(new Clonable<sf::RectangleShape>(sf::Vector2f{4, 4}));
std::unique_ptr<ClonableShape> shapeB = std::make_unique<Clonable<sf::CircleShape>>(4.f);

std::unique_ptr<ClonableShape> shapeC = shapeA->clone();
auto shapeD = shapeB->clone();

Since sf::Shape publicly derives from sf::Drawable and there are several functions in the SFML library that accept a reference to sf::Drawable, e.g., sf::RenderWindow::draw(), you may want to extend the ClonableShape interface above to being able to implicitly convert to sf::Drawable& by adding:

virtual operator sf::Drawable&() = 0;

and then overriding it in Clonable<> as:

operator sf::Drawable&() override { return *this; }

This way, if you have a function like:

void draw(sf::Drawable&);

You will be able to call it by just writing:

draw(*shapeA);

X If don't rely much on the concrete classes – e.g., if you don't work directly with Clonable<sf::RectangleShape> or Clonable<sf::CircleShape> – then you may want to switch from inheritance to composition as you won't be depending much on the member functions inherited by having the concrete shapes as public bases but rather on those you specify in ClonableShape:

template<typename T>
class Clonable: public ClonableShape {
    T shape_; // as data member instead of public base
public:
    Clonable() = default;

    template<typename... Args>
    Clonable(Args... args): shape_(args...) {}

    std::unique_ptr<ClonableShape> clone() const override {
        return std::make_unique<Clonable<T>>(*this);
    }

    const sf::Vector2f& getPosition() const override {
        // careful not to call ClonableShape::getPosition() instead
        return shape_.getPosition();
    }

    // operator sf::Drawable&() override { return shape_; } 
};
JFMR
  • 23,265
  • 4
  • 52
  • 76