Два существующих решения обменивают динамический полиморфизм на статический полиморфизм.Без более подробной информации о рассматриваемой проблеме невозможно определить, является ли это допустимым подходом или нет, поскольку он в основном нарушает полиморфную иерархию: с CRTP нет единого базового класса, а скорее их семейства.Вы не можете хранить объекты Derived1
и Derived2
в одном и том же контейнере, так как они не связаны ... Это хорошее решение, если все, что вам нужно, это поделиться кодом, но не если вам нужен динамический полиморфизм.Взгляните на шаблон Visitor и на двойную диспетчеризацию для подобных проблем.
Если вам нужен динамический полиморфизм, вы можете попытаться реализовать двойную диспетчеризацию (это неудобно, но выполнимо, если иерархия достаточно мала.По сути, создайте две разные иерархии, одна с корнем в Base
, а другая, которая не соответствует ручному диспетчеру. Иерархия с корнем в Base
будет иметь виртуальный метод apply
, а вторая иерархия будет иметь виртуальные функции для каждой из них.типов в первой иерархии:
class Base;
class Derived1; // inherits from Base, implements Visitor
class Derived2; // inherits from either Base or Derived2
struct Visitor {
virtual void visit( Base& ) = 0; // manually unrolled for all types
virtual void visit( Derived1& ) = 0;
virtual void visit( Derived2& ) = 0;
};
struct Base {
virtual void apply( Visitor& v ) { // manually replicate this in Derived1, 2
v.visit( *this );
}
template <typename T> void foo(T); // implement
};
template <typename T>
struct FooCaller : Visitor {
T& ref_value;
FooCaller( T& v ) : ref_value(v) {}
template <typename U> void call_foo( U& o ) {
o.foo(ref_value);
}
virtual void visit( Base & b ) { call_foo(b); }
virtual void visit( Derived1 & d1 ) { call_foo(d1); }
virtual void visit( Derived2 & d2 ) { call_foo(d2); }
};
Имена, которые я использовал, распространены в шаблоне Visitor, и этот подход очень похож на этот шаблон (я не смею называть его шаблоном Visitor,но подход аналогичен, поэтому я просто заимствовал соглашение об именах).
Код пользователя будет похож на:
int main() // main returns int, not void!!!
{
Base* BasePtr = new Derived1();
int i = 5;
FooCaller<int> c(i)
BasePtr->apply(c); // [1] magic happens here
}
Требование объявления i
и c
перед началомможет быть освобожден путем изменения (если возможно) аргументов функций из ссылок на const-ссылки.Фактическая магия заключается в том, что в [1] механизм одиночной диспетчеризации C ++ отправляет вызов Derived1::apply
, поскольку это динамический тип объекта, на который указывает BasePtr
.В этот момент он будет вызывать Visitor::visit( Derived1& )
с самим собой в качестве аргумента.Это снова будет отправлено через механизм единой отправки на FooCaller<int>::visit( Derived1& )
, и в этот момент оба объекта будут преобразованы в их статические типы.Когда FooCaller<int>::visit
вызывает call_foo
, аргумент U
выводится как Derived1
, когда он вызывает Derived1::foo
, аргумент выводится как int
и заканчивается тем, что вызывает Derived1::foo<int>
... хотя парациклов и косвенных значений ...
В зависимости от вашего конкретного случая использования это может быть слишком сложно (если бы работал статический полиморфизм, такой как CRTP) или слишком сложно поддерживать (если иерархия велика: для каждого нового элемента вВ иерархии Base
вам придется обновить всех типов в иерархии Visitor
), поэтому, если вы можете избежать этой сложности, идеально.В некоторых случаях, однако, вам это нужно.
Также обратите внимание, что это наиболее сложное полностью динамическое решение, между ними есть другие варианты, в зависимости от того, что вам нужно для полиморфизма во время выполнения ...Возможно, ваша иерархия моделирует посетителя с короткими замыканиями, и вам нужно только вручную развернуть различные виртуальные функции, которые будут отправляться внутри шаблона, в этом случае половина вышеуказанной сложности исчезнет.
Также обратите внимание, что в C ++ это довольно необычно, и если вы объясните реальную проблему, возможно, есть более простые решения, которые вы заявили, как требования вашего решения исходной проблемы: динамическая отправка в шаблон.