Как я уже сказал в своем комментарии, вы можете сделать это с помощью Boost.Variant . Есть и другой путь, более естественный, и это обычная иерархия классов с полиморфизмом.
Поскольку уже есть ответы на версию иерархии, я собираюсь сосредоточиться на варианте варианта здесь.
Сначала код, потом объяснение:
#include <iostream>
#include <boost/variant.hpp>
struct Cat{
void speak() const{ std::cout << "meow\n"; }
void cat_extra() const{ std::cout << "Special cat move!\n"; }
~Cat(){ std::cout << "Cat died\n"; }
};
struct Dog{
void speak() const{ std::cout << "wuff\n"; }
void dog_extra() const{ std::cout << "Special dog move!\n"; }
~Dog(){ std::cout << "Dog died\n"; }
};
struct Bird{
void speak() const{ std::cout << "chirp\n"; }
void bird_extra() const{ std::cout << "Special bird move!\n"; }
~Bird(){ std::cout << "Bird died\n"; }
};
struct Fish{
void speak() const{ std::cout << "blub\n"; }
void fish_extra() const{ std::cout << "Special fish move!\n"; }
~Fish(){ std::cout << "Fish died\n"; }
};
struct speak_visitor
: boost::static_visitor<void>
{
template<class Animal>
void operator()(Animal const* p) const{
p->speak();
}
};
struct extra_visitor
: boost::static_visitor<void>
{
void operator()(Cat const* p) const{
p->cat_extra();
}
void operator()(Dog const* p) const{
p->dog_extra();
}
void operator()(Bird const* p) const{
p->bird_extra();
}
void operator()(Fish const* p) const{
p->fish_extra();
}
};
struct delete_visitor
: boost::static_visitor<void>
{
template<class Animal>
void operator()(Animal const* p) const{
delete p;
}
};
int main(){
typedef boost::variant<Cat*, Dog*, Bird*, Fish*> variant_type;
variant_type var;
speak_visitor sv;
delete_visitor dv;
extra_visitor ev;
var = new Cat();
var.apply_visitor(sv);
var.apply_visitor(ev);
var.apply_visitor(dv);
var = new Dog();
var.apply_visitor(sv);
var.apply_visitor(ev);
var.apply_visitor(dv);
var = new Bird();
var.apply_visitor(sv);
var.apply_visitor(ev);
var.apply_visitor(dv);
var = new Fish();
var.apply_visitor(sv);
var.apply_visitor(ev);
var.apply_visitor(dv);
}
Пример запуска на Ideone .
Теперь перейдем к объяснению. A boost::variant
в основном является расширенным и помеченным union
. A union
может хранить разные вещи, но только по одному за раз. То же самое касается варианта. boost::variant
помечен, потому что вы можете узнать, какой тип хранится в данный момент, с помощью функции-члена which
, которая будет возвращать индекс на основе 0 в зависимости от того, какой тип хранится в данный момент. Связанная документация объяснит вариант лучше, чем я могу.
Для безопасного «посещения» сохраненного значения требуется конструкция с именем «посетитель». Идея для посетителей великолепна, и есть много ресурсов, которые ее описывают. Связанная документация также входит в некоторые детали здесь. По существу, вариант внутренне выполняет switch
для сохраненного типа (с which
) и вызывает вариант operator()
с соответствующим аргументом, что-то вроде этого:
// something similar to this happens internally in `apply_visitor`
// contrived for our example classes here
template<class V>
void variant::apply_visitor(V& v){
switch(this->which()){
case 0: v((Cat*)&(this->internal_storage)); break;
case 1: v((Dog*)&(this->internal_storage)); break;
case 2: v((Bird*)&(this->internal_storage)); break;
case 3: v((Fish*)&(this->internal_storage)); break;
}
}
Благодаря этому вызывается соответствующий operator()
с соответствующим аргументом. Это также позволяет компилятору отлавливать возможную ошибку, когда один из типов вариантов не обрабатывается предоставленным посетителем, потому что нет подходящей перегрузки для operator()
.
Эта концепция может быть не совсем понятна при использовании общих посетителей (speak_visitor
, delete_visitor
), но она разъясняется extra_visitor
. Если вы закомментируете там одну из operator()
перегрузок, компилятор будет раздражен.
Если у вас есть какие-либо вопросы, пожалуйста, дайте мне знать в комментариях. И последнее, но не менее важное, я могу только повторить, что вы должны прочитать документацию. :)