Где вы находите шаблоны полезными?

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

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

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

Ответы

Ответ 1

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

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

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

Ответ 2

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

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

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

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

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

Вы можете реализовать функцию 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
}

Ответ 3

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

Одним из объектов, предоставляемых 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

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

Ответ 5

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

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;
}

Ответ 6

Шаблоны, которые я использую, представляют собой множество классов контейнеров, повышают интеллектуальные указатели, 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)

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


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

Ответ 7

Мы используем 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;
}

Ответ 8

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

Ответ 9

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

Теперь, что столь изящно относится к этой функции. Подумайте, что вы пишете интерфейс для других пользователей. Вы знаете, что ваш интерфейс будет использоваться, потому что это модуль в своем собственном домене. Но вы еще не знаете, как люди будут его использовать. Разработка на основе политик укрепляет ваш код для повторного использования в будущем; это делает вас независимыми от типов данных, на которые опирается конкретная реализация. Код просто "разрывается".: -)

Черты сами по себе являются прекрасной идеей. Они могут привязывать к модели определенное поведение, данные и typedata. Черты допускают полную параметризацию всех этих трех полей. И самое лучшее, это очень хороший способ сделать код повторно используемым.

Ответ 10

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

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) ;
}

Etc.

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

Ответ 11

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

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

Ответ 12

Я лично использовал шаблон Curiously 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));
};