При поиске ответа на эту проблему я наткнулся и на этот, и на похожий вопрос . В ответах на другой вопрос вы найдете два предложенных решения:
- Используйте std :: option или boost :: option и шаблон посетителя. Это решение затрудняет добавление новых типов, но легко добавляет новые функциональные возможности.
- Используйте класс-оболочку, подобный тому, что Шон Родитель представляет в своем выступлении . Это решение затрудняет добавление новых функций, но позволяет легко добавлять новые типы.
Оболочка определяет интерфейс, который вам нужен для ваших классов, и содержит указатель на один такой объект. Реализация интерфейса осуществляется с помощью бесплатных функций.
Вот пример реализации этого шаблона:
class Shape
{
public:
template<typename T>
Shape(T t)
: container(std::make_shared<Model<T>>(std::move(t)))
{}
friend void draw(const Shape &shape)
{
shape.container->drawImpl();
}
// add more functions similar to draw() here if you wish
// remember also to add a wrapper in the Concept and Model below
private:
struct Concept
{
virtual ~Concept() = default;
virtual void drawImpl() const = 0;
};
template<typename T>
struct Model : public Concept
{
Model(T x) : m_data(move(x)) { }
void drawImpl() const override
{
draw(m_data);
}
T m_data;
};
std::shared_ptr<const Concept> container;
};
Различные формы затем реализуются как обычные структуры / классы. Вы можете сами выбирать, хотите ли вы использовать функции-члены или бесплатные функции (но вам придется обновить вышеуказанную реализацию, чтобы использовать функции-члены). Я предпочитаю бесплатные функции:
struct Circle
{
const double radius = 4.0;
};
struct Rectangle
{
const double width = 2.0;
const double height = 3.0;
};
void draw(const Circle &circle)
{
cout << "Drew circle with radius " << circle.radius << endl;
}
void draw(const Rectangle &rectangle)
{
cout << "Drew rectangle with width " << rectangle.width << endl;
}
Теперь вы можете добавлять объекты Circle
и Rectangle
к одному и тому же std::vector<Shape>
:
int main() {
std::vector<Shape> shapes;
shapes.emplace_back(Circle());
shapes.emplace_back(Rectangle());
for (const auto &shape : shapes) {
draw(shape);
}
return 0;
}
Недостатком этого шаблона является то, что он требует большого количества шаблонов в интерфейсе, поскольку каждую функцию необходимо определять три раза.
Плюс в том, что вы получаете семантику копирования:
int main() {
Shape a = Circle();
Shape b = Rectangle();
b = a;
draw(a);
draw(b);
return 0;
}
Это производит:
Drew rectangle with width 2
Drew rectangle with width 2
Если вас беспокоит shared_ptr
, вы можете заменить его на unique_ptr
.
Однако он больше не будет копироваться, и вам придется либо перемещать все объекты, либо осуществлять копирование вручную.
Шон Родитель подробно обсуждает это в своем выступлении, а реализация показана в вышеупомянутом ответе.