Вложенные определения шаблона C ++ - PullRequest
1 голос
/ 14 мая 2009

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

Во-первых, вот два класса, которые у меня есть

template<class DataType> class Class1
{
    //Lots of stuff here
}

template<Class DataType> class Class2
{
    //The same stuff as in Class1, but implemented differently
}

В типичном oo-дизайне Class1 и Class2 наследуются от IInterface, и я мог бы иметь функцию, которая выглядит следующим образом

DoStuff(IInterface& MyInterface)
{
}

Но я не могу этого сделать, поэтому я сделал это

template <class C>
DoStuff(C& c)
{
}

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

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

)

Например, это работает прямо сейчас

DoStuff(char* filename)
{
    switch (//figure out the type i need to make)
    {
    case 1: return DoStuff(Class1<int>(filename));
    case 2: return DoStuff(Class1<double>(filename));
    }
}

template<class DataType>
DoStuff(DataType* pdata)
{
    return DoStuff(Class2<DataType>(pdata));
}

template<class C>
DoStuff(C c)
{
  c.Print();
}

Теперь я знаю, что вы спрашиваете, зачем использовать Class1 и Class2? Что ж, основное различие между работой с файлом и работой с памятью настолько велико, что имеет смысл иметь разные классы для разных типов ввода (а не просто перегружать конструктор и заставлять его вести себя по-разному для разных входных данных). Опять же, я проверил это, и гораздо быстрее обрабатывать особые случаи в своих собственных классах, а не иметь case s / if s в каждой функции.

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

Проблема, с которой я столкнулся, заключается в том, что функция DoStuff, которая выполняет эту работу, только шаблонизируется <class C>, а сама C - <class DataType>, и все, что я не могу понять, как передать все в общий способ. Например, я не могу использовать template <class C<DataType>> или template<template< class DataType> class C>. Это просто не скомпилируется.

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

BigSwitch(CallBack,Inputs)
{
    switch(//something)
    {
    case 1: return CallBack(Class1<Type>(Inputs))
    case 2: return CallBack(Class2<Type>(Inputs))
    }
}

Таким образом, я могу написать одну BigSwitch функцию, а другие люди будут писать функции CallBack.

Есть идеи?


РЕДАКТИРОВАТЬ для разъяснения для Джальфа:

У меня есть два очень похожих класса, Class1 и Class2, которые представляют в основном один и тот же тип данных, однако хранилище данных сильно отличается. Чтобы сделать его более конкретным, я буду использовать простой пример: Class1 - это простой массив, а Class2 выглядит как массив, но вместо того, чтобы хранить в памяти, он хранится в файле (потому что он слишком большой, чтобы поместиться в памяти) , Я позвоню им MemArray и FileArray прямо сейчас. Допустим, я хотел сумму массивов. Я могу сделать что-то вроде этого

template <class ArrayType, class ReturnType>
ReturnType Sum(ArrayType A)
{
    ReturnType S=0;
    for (int i=A.begin();i<A.end();++i)
    {
      S+=A[i];
    }
    return S;
}

Но теперь мне нужен способ загрузки реальных данных в массив. Если это массив на основе памяти, я бы сделал это

MemArray<DataType> M(pData);

и если это файл заблокирован, я бы сделал это

FileArray<DataType> F(filename);

и оба эти вызова действительны (поскольку компилятор генерирует оба пути кода во время компиляции)

double MS=Sum<MemArray<DataType>,double>(M);
double FS=Sum<FileArray<DataType>,double>(F);

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

double GetSum(char* filename)
{
    int DataTypeCode=GetDataTypeCode(filename);
    switch (DataTypeCode)
    {
    case 1: return Sum<FileArray<int>,double>(FileArray<int>(filename));
    case 2: return Sum<FileArray<double>,double>(FileArray<double>(filename));
    }
}
template <class DataType>
double GetSum(DataType* pData)
{
    return Sum<MemArray<DataType>,double>(MemArray<DataType>(pData));
}

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

double GetX(CallBackType X, char* filename)
{
    int DataTypeCode=GetDataTypeCode(filename);
    switch (DataTypeCode)
    {
    case 1: return X<FileArray<int>,double>(FileArray<int>(filename));
    case 2: return X<FileArray<double>,double>(FileArray<double>(filename));
    }
}
template <class DataType>
double GetX(CallBackType, DataType* pData)
{
    return X<MemArray<DataType>,double>(MemArray<DataType>(pData));
}

чтобы я мог позвонить

GetX(Sum,filename)

потом, когда кто-то еще хочет добавить новую функцию, все, что ему нужно сделать, это написать функцию и вызвать

GetX(NewFunction,filename)

Я просто ищу способ написать мои перегруженные GetX функции и мои X функции, чтобы можно было абстрагировать ввод / хранение от реальных алгоритмов. Обычно это не сложная проблема, просто у меня проблемы, потому что функция X содержит шаблонный аргумент, который сам шаблонизируется. template<class ArrayType> также имеет скрытое ArrayType<DataType>, скрытое там. Компилятор недоволен этим.

Ответы [ 2 ]

3 голосов
/ 14 мая 2009

Сосредоточение внимания на начальной части вашего вопроса (почему вы не просто используете наследование):

Распространенный способ сделать полиморфизм во время компиляции и предоставить доступ к членам производного класса через базовый класс - это шаблон CRTP .

template <typename T>
class IInterface {
  void DoStuff() {
    void static_cast<T*>(this)->DoStuff()
  }
};

class Class1 : IInterface<Class1> {
  void DoStuff(){...}
}

Это решит твою проблему?

Edit: Кстати, я рад, что смог помочь, но в следующий раз, пожалуйста, постарайтесь структурировать ваш вопрос немного подробнее.

Я действительно понятия не имел, что вы спрашиваете, так что это был просто удар в темноте, основанный на первых 3 строках вашего вопроса. ;)

Вы никогда не объясняете, чего пытаетесь достичь, только как выглядит ваш неработающий обходной путь. Начните с постановки проблемы, так как это то, что нам действительно нужно знать. Затем вы можете предоставить подробную информацию о ваших текущих обходных путей. А при публикации кода добавьте некоторый контекст. Откуда вызывается DoStuff (), и зачем младшим разработчикам их определять? (Вы уже сделали это, не так ли?)

Что сказали бы начинающие разработчики, прежде всего, с этим кодом?

И сбивает с толку то, что вы предоставляете конкретные случаи (1 и 2), но не сам оператор switch (// что-то)

В следующий раз вы получите гораздо больше (и лучше, и быстрее) ответов, если постараетесь облегчить ответ человеку. :)

0 голосов
/ 14 мая 2009

Что касается вашего вопроса об «обобщенном обратном вызове», вы можете использовать boost :: function , но он по существу использует виртуальные функции под прикрытием (это может и не быть - но, по крайней мере, похожая концепция), поэтому разницы в производительности, которую вы ищете, не будет (фактически, boost :: function, вероятно, будет медленнее из-за выделения кучи).

...