Где вы найдете полезные шаблоны?
-
04-07-2019 - |
Вопрос
На моем рабочем месте мы обычно используем 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));
};
Уже упоминалось, что вы можете использовать шаблоны в качестве классов политики, чтобы что-то делать . Я часто этим пользуюсь.
Я также использую их с помощью карт свойств ( см. сайт поддержки для получения дополнительной информации об этом ), чтобы получить общий доступ к данным. Это дает возможность изменить способ хранения данных без необходимости изменять способ их получения.