вызов функции шаблона производного класса - PullRequest
5 голосов
/ 17 мая 2011

У меня проблема в C ++ с вызовом функции производного класса при наличии указателя на базовый класс.

РЕДАКТИРОВАТЬ: Некоторые ответы ссылались на CRTP

но я хочу сказать, что мне нужен указатель на класс «Base *», а не «Base *», потому что я не знаю, какой тип обрабатывается в данный момент (текущий экземпляр создается из какой-то фабрики)).

Классы:

class Base 
{
..
template<typename T>
func (T arg) { ... };
};

class Derived1 : public Base
{
...
template<typename T>
func (T arg) { ... };
};


class Derived1 : public Base
{
...
template<typename T>
func (T arg) { ... };
};

Использование:

int main()
{
   Base* BasePtr = new Derived1();

   // The expected function to be called is Derived1::func<int>()
   BasePtr->func<int>();

   return 0; // :)
}

Я не могу сделать func виртуальным, поскольку язык не поддерживает функцию виртуального шаблона.

Это допустимо только в том случае, если только у класса есть аргументы шаблона, но только если у функции внутри него есть аргументы шаблона.

Я видел похожую проблему, решенную в Boost.Serialization, но не смог понять решение.

Спасибо,

Коби Меир

Ответы [ 3 ]

7 голосов
/ 17 мая 2011

Реализация Любопытно повторяющийся шаблон (CTRP) .

Иллюстрация:

template<typename D>
class Base 
{
public:
    template<typename T>
    void func (T arg) 
    {
         static_cast<D*>(this)->func(arg);
    }
};

class Derived1 : public Base<Derived1>
{
public:
    template<typename T>
    void func (T arg) { /*...*/ }
};


Base<Derived1> *basePtr = new Base<Derived1>();
basePtr->func(100);
6 голосов
/ 17 мая 2011

Два существующих решения обменивают динамический полиморфизм на статический полиморфизм.Без более подробной информации о рассматриваемой проблеме невозможно определить, является ли это допустимым подходом или нет, поскольку он в основном нарушает полиморфную иерархию: с 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 ++ это довольно необычно, и если вы объясните реальную проблему, возможно, есть более простые решения, которые вы заявили, как требования вашего решения исходной проблемы: динамическая отправка в шаблон.

3 голосов
/ 17 мая 2011

Проверьте это , это поможет вам реализовать CRTP.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...