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?
For the sake of this answer, let's assume you are working with the SFML's sf::Shape
abstract class:
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_; }
};