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

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

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

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


person EvilTeach    schedule 26.10.2008    source источник


Ответы (12)


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

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

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

person Head Geek    schedule 26.10.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
}
person Brian R. Bondy    schedule 26.10.2008
comment
Итак, вы использовали шаблоны для факториалов в реальной жизни? - person Roddy; 26.10.2008
comment
Не в реальной жизни :) Но идею можно расширить во многие области реальной жизни. - person Brian R. Bondy; 26.10.2008
comment
Итак, у вас есть пример, который вы ЛИЧНО использовали? - person Roddy; 26.10.2008

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

Одно из средств, которое предоставляет 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 ;
person Jackson    schedule 26.10.2008

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

person zvrba    schedule 26.10.2008
comment
Интересный. Я сделал нечто подобное, используя наследование базового класса с одним защищенным членом int, но класс шаблона избавил бы меня от небольшого дублирования кода, если бы я подумал об этом! - person Roddy; 26.10.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;
}
person Milan Babuškov    schedule 26.10.2008
comment
слишком верно. я сделал то же самое для wxConfig :) - person Johannes Schaub - litb; 08.12.2008

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

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

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

    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)

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


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

person peterchen    schedule 26.10.2008

Мы используем COM и принимаем указатель на объект, который может реализовать другой интерфейс напрямую или через [_1 _] (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;
}
person Motti    schedule 26.10.2008

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

person John D. Cook    schedule 26.10.2008

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

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

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

person mstrobl    schedule 26.10.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) ;
}

И Т. Д.

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

person paercebal    schedule 26.10.2008

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

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));
};
person Dean Michael    schedule 29.10.2008

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

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

person Benoît    schedule 27.10.2008