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_; }
};