Я действительно не знаю, как подойти к этому. Я не понимаю половину перечисленных вами требований к интерфейсу, поэтому рассмотрите этот экспериментальный ответ, который может вообще не относиться к вашей проблеме.
Я предлагаю вам сказать мне, чего именно не хватает в моем подходе, и я могу изменить это. Сейчас я опущу шаблоны, так как они не имеют отношения к проблеме.
Итак, без лишних слов, самый простой старт использует контейнер умных указателей:
#include <vector>
#include <memory>
struct Base
{
virtual void f();
};
typedef std::shared_ptr<Base> BasePtr;
typedef std::vector<BasePtr> BaseContainer;
struct DerivedA : Base
{
virtual void f();
// ...
};
// further derived classes
Использование:
int main()
{
BaseContainer v;
v.push_back(BasePtr(new DerivedB));
v.push_back(BasePtr(new DerivedC(true, 'a', Blue)));
BasePtr x(new DerivedA);
some_func(x);
x->foo()
v.push_back(x);
v.front()->foo();
}
Если у вас где-то есть какой-то автоматический объект, вы можете вставить копию:
DerivedD d = get_some_d();
v.push_back(BasePtr(new DerivedD(d)));
Для повторения:
for (BaseContainer::const_iterator it = v.begin(), end = v.end(); it != end; ++it)
{
(*it)->foo();
}
Обновление: Если вы хотите инициализировать объект после построения, вы можете сделать что-то вроде этого:
{
DerivedE * p = new DerivedE(x, y, z);
p->init(a, b, c);
v.push_back(BasePtr(p));
}
Или, если функция init
является виртуальной, еще проще:
v.push_back(BasePtr(new DerivedE(x, y, z)));
v.back()->init(a, b, c);
2-е обновление: Вот как может выглядеть производный объект:
struct DerivedCar : Base
{
enum EType { None = 0, Porsche, Dodge, Toyota };
DerivedCar(EType t, bool a, unsigned int p)
: Base(), type(t), automatic_transmission(a), price(p)
{
std::cout << "Congratulations, you know own a " << names[type] << "!\n"; }
}
private:
EType type;
bool automatic_transmission;
unsigned int price;
static const std::unordered_map<EType, std::string> names; // fill it in elsewhere
};
Использование: Base * b = new DerivedCar(DerivedCar::Porsche, true, 2000);
3-е обновление: Это не связано, просто иллюстрация того, как использовать таблицы поиска в пользу операторов switch. Предположим, у нас есть много похожих функций (одна и та же сигнатура), которые мы хотим использовать на основе некоторого целого числа:
struct Foo
{
void do_a();
void do_b();
// ...
void do(int n)
{
switch (n) {
case 2: do_a(); break;
case 7: do_b(); break;
}
}
};
Вместо переключателя мы можем зарегистрировать все функции в таблице поиска. Здесь я предполагаю поддержку C ++ 11:
struct Foo
{
// ...
static const std::map<int, void(Foo::*)()> do_fns;
void do(int n)
{
auto it = do_fns.find(n);
if (it != do_fns.end()) { (this->**it)(); }
}
};
const std::map<nt, void(Foo::*)()> Foo::do_fns {
{ 3, &Foo::do_a },
{ 7, &Foo::do_b },
// ...
};
По сути, вы превращаете статический код в контейнер data . Это всегда хорошо. Теперь это легко масштабируется; вы просто добавляете новые функции на карту поиска по мере их появления. Не нужно снова касаться действительного do()
кода!