Где вы находите шаблоны полезными? - PullRequest
15 голосов
/ 26 октября 2008

На моем рабочем месте мы обычно используем iostream , string , vector , map и нечетный алгоритм или два. На самом деле мы не нашли много ситуаций, когда шаблонные методы были бы лучшим решением проблемы.

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

В качестве взятки, ожидайте ответа за ваш голос.

Ответы [ 12 ]

11 голосов
/ 26 октября 2008

Общая информация о шаблонах:

Шаблоны полезны в любое время, когда вам нужно использовать один и тот же код, но работающий с разными типами данных, где типы известны во время компиляции. А также, когда у вас есть какой-либо объект-контейнер.

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

Другое очень распространенное использование - алгоритмы сортировки.

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

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

А теперь самое интересное:

Также см. шаблон метапрограммирования , который является способом предварительной оценки кода во время компиляции, а не во время выполнения. Шаблонное метапрограммирование имеет только неизменные переменные, и поэтому его переменные не могут изменяться. Из-за этого шаблона метапрограммирование можно рассматривать как тип функционального программирования.

Посмотрите на этот пример шаблонного метапрограммирования из Википедии. Он показывает, как шаблоны могут использоваться для выполнения кода во время компиляции . Поэтому во время выполнения у вас есть заранее вычисленная константа.

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

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
    int x = Factorial<4>::value; // == 24
    int y = Factorial<0>::value; // == 1
}
8 голосов
/ 26 октября 2008

Я использовал много шаблонов кода, в основном в Boost и STL, но мне редко приходилось писать любой.

Одно из исключений, несколько лет назад, было в программе, которая манипулировала EXE-файлами в формате Windows PE. Компания хотела добавить 64-битную поддержку, но класс ExeFile, который я написал для работы с файлами, работал только с 32-битными. Код, необходимый для манипулирования 64-разрядной версией, был по существу идентичным, но для него требовалось использовать другой тип адреса (64-разрядный вместо 32-разрядного), что также привело к тому, что две другие структуры данных также были разными.

Основываясь на использовании STL одного шаблона для поддержки std::string и std::wstring, я решил попробовать сделать ExeFile шаблоном с различными структурами данных и типом адреса в качестве параметров. Было два места, где мне все еще приходилось использовать #ifdef WIN64 строки (немного другие требования к обработке), но это было не так сложно сделать. Теперь у нас есть полная 32- и 64-битная поддержка в этой программе, и использование шаблона означает, что каждое изменение, которое мы сделали с тех пор, автоматически применяется к обеим версиям.

7 голосов
/ 26 октября 2008

Единственное место, где я использую шаблоны для создания собственного кода, - это реализация классов политик, как описано Андреем Александреску в Modern C ++ Design. В настоящее время я работаю над проектом, который включает в себя набор классов, которые взаимодействуют с монитором Tuxedo TP компании Oracle BEA \ h \ h \ h.

Одним из средств, предоставляемых Tuxedo, являются постоянные транзакционные очереди, поэтому у меня есть класс TpQueue, который взаимодействует с очередью:

class TpQueue {
public:
   void enqueue(...)
   void dequeue(...)
   ...
}

Однако, поскольку очередь является транзакционной, мне нужно решить, какое поведение транзакции я хочу; это можно сделать отдельно вне класса TpQueue, но я думаю, что он более явный и менее подвержен ошибкам, если каждый экземпляр TpQueue имеет свою собственную политику в отношении транзакций. Итак, у меня есть набор классов TransactionPolicy, таких как:

class OwnTransaction {
public:
   begin(...)  // Suspend any open transaction and start a new one
   commit(..)  // Commit my transaction and resume any suspended one
   abort(...)
}

class SharedTransaction {
public:
   begin(...)  // Join the currently active transaction or start a new one if there isn't one
   ...
}

И класс TpQueue переписывается как

template <typename TXNPOLICY = SharedTransaction>
class TpQueue : public TXNPOLICY {
   ...
}

Так что внутри TpQueue я могу при необходимости вызывать begin (), abort (), commit (), но могу изменить поведение в зависимости от способа объявления экземпляра:

TpQueue<SharedTransaction> queue1 ;
TpQueue<OwnTransaction> queue2 ;
4 голосов
/ 26 октября 2008

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

3 голосов
/ 26 октября 2008

Вот один пример из реального проекта. У меня есть функции получения, как это:

bool getValue(wxString key, wxString& value);
bool getValue(wxString key, int& value);
bool getValue(wxString key, double& value);
bool getValue(wxString key, bool& value);
bool getValue(wxString key, StorageGranularity& value);
bool getValue(wxString key, std::vector<wxString>& value);

А затем вариант со значением «по умолчанию». Возвращает значение ключа, если оно существует, или значение по умолчанию, если его нет. Шаблон избавил меня от необходимости создавать 6 новых функций самостоятельно.

template <typename T>
T get(wxString key, const T& defaultValue)
{
    T temp;
    if (getValue(key, temp))
        return temp;
    else
        return defaultValue;
}
2 голосов
/ 26 октября 2008

Мы используем COM и принимаем указатель на объект, который может реализовать другой интерфейс напрямую или через [IServiceProvider] (http://msdn.microsoft.com/en-us/library/cc678965(VS.85).aspx). Это побудило меня создать эту вспомогательную функцию, подобную приведению.

// Get interface either via QueryInterface of via QueryService
template <class IFace>
CComPtr<IFace> GetIFace(IUnknown* unk)
{
    CComQIPtr<IFace> ret = unk; // Try QueryInterface
    if (ret == NULL) { // Fallback to QueryService
        if(CComQIPtr<IServiceProvider> ser = unk)
            ser->QueryService(__uuidof(IFace), __uuidof(IFace), (void**)&ret);
    }
    return ret;
}
2 голосов
/ 26 октября 2008

Шаблоны, которые я регулярно использую, - это множество классов контейнеров, улучшенные интеллектуальные указатели, scopeguards , несколько алгоритмов STL.

Сценарии, в которых я написал шаблоны:

  • нестандартные контейнеры
  • управление памятью, реализация безопасности типов и вызов CTor / DTor поверх void * allocators
  • общая реализация для перегрузок разных типов, например

    bool ContainsNan (float *, int) bool ContainsNan (double *, int)

которые оба просто вызывают (локальную, скрытую) вспомогательную функцию

template <typename T>
bool ContainsNanT<T>(T * values, int len) { ... actual code goes here } ;

Конкретные алгоритмы, которые не зависят от типа, если тип обладает определенными свойствами, например, двоичная сериализация.

template <typename T>
void BinStream::Serialize(T & value) { ... }

// to make a type serializable, you need to implement
void SerializeElement(BinStream & strean, Foo & element);
void DeserializeElement(BinStream & stream, Foo & element)

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


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

1 голос
/ 29 октября 2008

Я лично использовал шаблон Curily Recurring Template Pattern как средство реализации некой формы нисходящего проектирования и реализации снизу вверх. Примером может служить спецификация универсального обработчика, в котором определенные требования как к форме, так и к интерфейсу применяются к производным типам во время компиляции. Это выглядит примерно так:

template <class Derived>
struct handler_base : Derived {
  void pre_call() {
    // do any universal pre_call handling here
    static_cast<Derived *>(this)->pre_call();
  };

  void post_call(typename Derived::result_type & result) {
    static_cast<Derived *>(this)->post_call(result);
    // do any universal post_call handling here
  };

  typename Derived::result_type
  operator() (typename Derived::arg_pack const & args) {
    pre_call();
    typename Derived::result_type temp = static_cast<Derived *>(this)->eval(args);
    post_call(temp);
    return temp;
  };

};

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

struct my_handler : handler_base<my_handler> {
  typedef int result_type; // required to compile
  typedef tuple<int, int> arg_pack; // required to compile
  void pre_call(); // required to compile
  void post_call(int &); // required to compile
  int eval(arg_pack const &); // required to compile
};

Это позволяет вам иметь универсальные полиморфные функции, которые работают только с производными типами handler_base <>:

template <class T, class Arg0, class Arg1>
typename T::result_type
invoke(handler_base<T> & handler, Arg0 const & arg0, Arg1 const & arg1) {
  return handler(make_tuple(arg0, arg1));
};
1 голос
/ 27 октября 2008

Уже упоминалось, что вы можете использовать шаблоны в качестве классов политики, чтобы что-то делал . Я часто этим пользуюсь.

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

1 голос
/ 26 октября 2008

Я однажды увидел следующий код:

void doSomethingGeneric1(SomeClass * c, SomeClass & d)
{
   // three lines of code
   callFunctionGeneric1(c) ;
   // three lines of code
}

повторяется десять раз:

void doSomethingGeneric2(SomeClass * c, SomeClass & d)
void doSomethingGeneric3(SomeClass * c, SomeClass & d)
void doSomethingGeneric4(SomeClass * c, SomeClass & d)
// Etc

Каждая функция имеет одинаковые 6 строк кода, копируется / вставляется и каждый раз вызывает другую функцию callFunctionGenericX с одинаковым числовым суффиксом.

Не было никакого способа полностью изменить весь процесс. Поэтому я сохранил местный рефакторинг.

Я изменил код следующим образом (из памяти):

template<typename T>
void doSomethingGenericAnything(SomeClass * c, SomeClass & d, T t)
{
   // three lines of code
   t(c) ;
   // three lines of code
}

И изменил существующий код:

void doSomethingGeneric1(SomeClass * c, SomeClass & d)
{
   doSomethingGenericAnything(c, d, callFunctionGeneric1) ;
}

void doSomethingGeneric2(SomeClass * c, SomeClass & d)
{
   doSomethingGenericAnything(c, d, callFunctionGeneric2) ;
}

1017 * Etc. *

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

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