Вопрос

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

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

В качестве взятки ожидайте, что за ваш ответ проголосуют «за».

Это было полезно?

Решение

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

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

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

Другие советы

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

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

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

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

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

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

Одно из мест, где я использую шаблоны для создания своего собственного кода, - это реализация классов политики, как описано Андреем Александреску в Modern C ++ Design. В настоящее время я работаю над проектом, который включает в себя набор классов, которые взаимодействуют с монитором Tuxedo TP компании 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 ;

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

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

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

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

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

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

    Bool содержит (float *, int) bool содержит (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)

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


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

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

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

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

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

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

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

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

И т. д.

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top