Может ли шаблонный полиморфизм использоваться вместо полиморфизма ОО? - PullRequest
22 голосов
/ 31 июля 2009

Я пытаюсь разобраться в применении шаблонного программирования (и в будущем - шаблонного метапрограммирования) в реальных сценариях. Одна проблема, которую я обнаружил, заключается в том, что шаблоны C ++ и полиморфизм не всегда играют вместе, как я хочу.

У меня вопрос, является ли способ, которым я пытаюсь применить шаблонное программирование, неправильным (и я должен использовать простой старый ООП), или я все еще застрял в мышлении ООП.

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

Код ООП с использованием композиции:

class Interpolator {
   public:
     Interpolator(ICacheStrategy* const c, IDataSource* const d);
     Value GetValue(const double);
}

void main(...) {
    Interpolator* i;
    if(param==1)
       i = new Interpolator(new InMemoryStrategy(...), new TextFileDataSource(...));
    else if(param==2)
       i = new Interpolator(new InMemoryStrategy(...), new OdbcDataSource(...));
    else if(param==3)
       i = new Interpolator(new NoCachingStrategy(...), new RestDataSource(...));

    while(run) {
       double input = WaitForRequest();
       SendRequest( i->GetValue(input));
    }
}

Потенциальная версия шаблона:

class Interpolator<class TCacheStrategy, class TDataSource> {
   public:
     Interpolator();
     Value GetValue(const double);               //may not be the best way but
     void ConfigCache(const& ConfigObject);      //just to illustrate Cache/DS         
     void ConfigDataSource(const& ConfigObject); //need to configured

}

//Possible way of doing main?
void main(...) {
    if(param==1)
       DoIt(Interpolator<InMemoryStrategy,TextFileDataSource>(),c,d);
    else if(param==2)
       DoIt(Interpolator<InMemoryStrategy,OdbcDataSource>(),c,d)
    else if(param==3)
       DoIt(Interpolator<NoCachingStrategy,RestDataSource>(),c,d)

}

template<class T>
void DoIt(const T&  t, ConfigObject c, ConfigObject d) {
   t.ConfigCache(c);
   t.ConfigDataSource(c);
   while(run) {
      double input = WaitForRequest();
      SendRequest( t.GetValue(input));
   }
}

Когда я пытаюсь преобразовать реализацию ООП в реализацию на основе шаблонов, код Интерполатора можно перевести без особых проблем. По сути, замените «интерфейсы» параметрами типа шаблона и добавьте механизм для передачи экземпляра Strategy / DataSource или параметров конфигурации.

Но когда я перехожу к «основному», мне не ясно, как это должно быть написано, чтобы использовать преимущества шаблонов в стиле шаблонного метапрограммирования. Я часто хочу использовать полиморфизм, но, похоже, он не очень хорошо работает с шаблонами (иногда мне кажется, что мне нужны дженерики Java для стирания типов ... тьфу).

Когда я часто нахожу, что хочу сделать, есть что-то вроде TemplateType<?,?> x = new TemplateType<X,Y>(), где x не волнует, что такое X, Y.

На самом деле, это часто моя проблема при использовании шаблонов.

  1. Нужно ли применять еще один уровень шаблоны?
  2. Я пытаюсь использовать свой новый блестящий гаечный ключ установить гвоздь ООП в слот PCI?
  3. Или я просто думаю об этом неправильно, когда дело доходит до шаблона программирование?

[Редактировать] Несколько человек отметили, что на самом деле это не шаблонное метапрограммирование, поэтому я немного перефразировал вопрос. Возможно, это часть проблемы - я еще не понял, что такое TMP.

Ответы [ 3 ]

24 голосов
/ 31 июля 2009

Шаблоны обеспечивают статический полиморфизм: вы указываете параметр шаблона во время компиляции, реализуя стратегию. Они не обеспечивают динамический полиморфизм, когда вы снабжаете объект во время выполнения виртуальными функциями-членами, которые реализуют стратегию.

Ваш пример шаблона кода создаст три разных класса, каждый из которых содержит весь код Интерполатора, скомпилированный с использованием различных параметров шаблона и, возможно, включения кода из них. Это, вероятно, не то, что вы хотите от POV размера кода, хотя в этом нет ничего категорически неправильного. Предположим, что вы оптимизировали, чтобы избежать накладных расходов на вызов функции, тогда это может быть улучшением динамического полиморфизма. Скорее, это излишне. Если вы хотите использовать шаблон стратегии динамически, то вам не нужны шаблоны, просто выполняйте виртуальные вызовы там, где это необходимо.

Вы не можете иметь переменную типа MyTemplate<?> (кроме появления в другом шаблоне до его создания). MyTemplate<X> и MyTemplate<Y> - это совершенно не связанные классы (даже если X и Y связаны), которые, возможно, просто имеют схожие функции, если они создаются из одного и того же шаблона (которым они не должны быть - один может быть специализация). Даже если это так, если параметр шаблона участвует в сигнатурах любой из функций-членов, тогда эти функции не совпадают, они просто имеют одинаковые имена. Таким образом, из POV динамического полиморфизма экземпляры одного и того же шаблона находятся в той же позиции, что и любые два класса - они могут воспроизводиться, только если вы дадите им общий базовый класс с некоторыми виртуальными функциями-членами.

Итак, вы можете определить общий базовый класс:

class InterpolatorInterface {
public:
    virtual Value GetValue(const double) = 0;
    virtual void ConfigCache(const& ConfigObject) = 0;
    virtual void ConfigDataSource(const& ConfigObject) = 0;
    virtual ~InterpolatorInterface() {}
};

Тогда:

template <typename TCacheStrategy, typename TDataSource>
class Interpolator: public InterpolatorInterface {
    ...
};

Теперь вы используете шаблоны для создания различных типов интерполяторов в соответствии с тем, что известно во время компиляции (поэтому вызовы от интерполятора к стратегиям не виртуальны), и вы используете динамический полиморфизм для их обработки одинаково даже если до времени выполнения вы не знаете, какой из них вы хотите (поэтому вызовы от клиента к интерполятору являются виртуальными). Вам просто нужно помнить, что эти два метода в значительной степени совершенно независимы, и решения о том, где использовать каждый из них, в значительной степени не связаны.

Кстати, это не шаблонное метапрограммирование, это просто использование шаблонов.

Edit. Что касается TMP, вот канонический вводный пример:

#include <iostream>

template<int N>
struct Factorial {
    static const int value = N*Factorial<N-1>::value;
};

template<>
struct Factorial<0> {
    static const int value = 1;
};

int main() {
    std::cout << "12! = " << Factorial<12>::value << "\n";
}

Обратите внимание, что 12! был вычислен компилятором и является константой времени компиляции. Это интересно, потому что оказывается, что система шаблонов C ++ является языком программирования, полным по Тьюрингу, а препроцессор C - нет. С учетом ограничений на ресурсы вы можете выполнять произвольные вычисления во время компиляции, избегая накладных расходов времени выполнения в ситуациях, когда вы знаете входные данные во время компиляции. Шаблоны могут манипулировать параметрами своего шаблона, как функциональный язык, а параметры шаблона могут быть целыми числами или типами. Или функции, хотя они не могут быть вызваны во время компиляции. Или другие шаблоны, хотя их нельзя «вернуть» как статические члены структуры.

7 голосов
/ 31 июля 2009

Я считаю, что шаблоны и полиморфизм хорошо работают вместе. В вашем примере, если клиентскому коду не важно, какие параметры шаблона использует Interpolator, введите абстрактный базовый класс, который подклассы шаблона. E.g.:

class Interpolator
{
public:
    virtual Value GetValue (const double) = 0;
};

template<class TCacheStrategy, class TDataSource>
class InterpolatorImpl : public Interpolator
{
public:
     InterpolatorImpl ();
     Value GetValue(const double);
};

void main()
{
    int param = 1;

    Interpolator* interpolator = 0;

    if (param==1)
        interpolator = new InterpolatorImpl<InMemoryStrategy,TextFileDataSource> ();
    else if (param==2)
        interpolator = new InterpolatorImpl<InMemoryStrategy,OdbcDataSource> ();
    else if (param==3)
        interpolator = new InterpolatorImpl<NoCachingStrategy,RestDataSource> ();

    while (true)
    {
        double input = WaitForRequest();
        SendRequest( interpolator->GetValue (input));
    }
}

Я часто использую эту идиому. Это довольно хорошо скрывает шаблонные вещи от клиентского кода.

Обратите внимание, я не уверен, что это использование шаблонов действительно как "метапрограммирование". Я обычно резервирую этот грандиозный термин для использования более сложных шаблонных приемов времени компиляции, особенно использования условных выражений, рекурсивных определений и т. Д. Для эффективного вычисления материала во время компиляции.

6 голосов
/ 31 июля 2009

Шаблоны иногда называют статическим (или компиляционным) полиморфизмом, так что да, их иногда можно использовать вместо ООП (динамического) полиморфизма. Конечно, он требует, чтобы типы определялись во время компиляции, а не во время выполнения, поэтому он не может полностью заменить динамический полиморфизм.

Когда я часто нахожу, что хочу сделать, это иметь что-то вроде TemplateType x = new TemplateType (), где x не волнует, что такое X, Y.

Да, это невозможно. Вы должны сделать что-то похожее на то, что у вас есть с функцией DoIt (). Часто я думаю, что в любом случае это приводит к более чистому решению (в конечном итоге вы получаете более мелкие функции, которые выполняют только одну вещь - как правило, хорошо). Но если типы определяются только во время выполнения (как i в версии ООП вашей основной функции), то шаблоны работать не будут.

Но в этом случае я думаю, что ваша версия шаблона хорошо решает проблему и сама по себе является хорошим решением. (Хотя, как уже упоминалось, это означает, что код создается для всех трех шаблонов, что может в некоторых случаях быть проблемой)

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