У меня есть класс, поведение которого я пытаюсь настроить.
template<int ModeT, bool IsAsync, bool IsReentrant> ServerTraits;
Затем у меня есть сам объект сервера:
template<typename TraitsT>
class Server {...};
Мой вопрос для моего использования выше, мое имя неправильно названо? Является ли мой шаблонный параметр политикой, а не чертой характера?
Когда шаблонный аргумент является чертой против политики?
Политики — это классы (или шаблоны классов) для вводить поведение в родительский класс, как правило, через наследование. Разлагая родительский интерфейс на ортогональные (независимые) измерения, классы политики формируют строительные блоки более сложных интерфейсов. Часто встречающийся шаблон заключается в предоставлении политик в качестве определяемых пользователем параметров шаблона (или шаблона-шаблона) с предоставленными по умолчанию библиотекой. Примером из стандартной библиотеки являются Распределители, которые являются параметрами шаблона политики всех контейнеров STL
template<class T, class Allocator = std::allocator<T>> class vector;
Здесь Allocator
Параметр шаблона (который также является шаблоном класса!) внедряет политику выделения и освобождения памяти в родительский класс std::vector
, Если пользователь не предоставляет распределитель, по умолчанию std::allocator<T>
используется.
Как типично для основанного на шаблонах полиморфизма, требования интерфейса к классам политики неявный и семантический (основанный на действительных выражениях), а не явный и синтаксический (основанный на определении виртуальных функций-членов).
Обратите внимание, что более поздние неупорядоченные ассоциативные контейнеры имеют более одной политики. В дополнение к обычному Allocator
параметр шаблона, они также принимают Hash
политика, которая по умолчанию std::hash<Key>
функциональный объект. Это позволяет пользователям неупорядоченных контейнеров настраивать их по нескольким ортогональным измерениям (выделение памяти и хеширование).
Черты являются шаблонами классов для извлечь свойства из универсального типа. Существует два вида черт: однозначные и многозначные. Примеры однозначных признаков — те, что в заголовке <type_traits>
template< class T >
struct is_integral
{
static const bool value /* = true if T is integral, false otherwise */;
typedef std::integral_constant<bool, value> type;
};
Однозначные черты часто используются в Шаблон-метапрограммированием и трюки SFINAE для перегрузки шаблона функции, основанного на условии типа.
Примерами многозначных признаков являются iterator_traits и allocator_traits из заголовков <iterator>
а также <memory>
соответственно. Поскольку признаки являются шаблонами классов, они могут быть специализированными. Ниже приведен пример специализации iterator_traits
за T*
template<T>
struct iterator_traits<T*>
{
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
using iterator_category = std::random_access_iterator_tag;
};
Выражение std::iterator_traits<T>::value_type
делает возможным сделать универсальный код для полноценных классов итераторов, пригодных для использования даже для необработанных указателей (так как необработанные указатели не имеют члена value_type
).
При написании ваших собственных универсальных библиотек важно подумать о том, как пользователи могут специализировать ваши собственные шаблоны классов. Однако следует соблюдать осторожность, чтобы пользователи не стали жертвами Одно Правило Определения используя специализации черт, чтобы вводить, а не извлекать поведение. Перефразируя это старый пост Андрей Александреску
Основная проблема заключается в том коде, который не видит специализированный
версия черты будет по-прежнему компилироваться, скорее всего, ссылка, и
иногда может даже бежать. Это потому, что в отсутствие
явная специализация, не специализированный шаблон,
реализация общего поведения, которое работает для вашего особого случая, как
Что ж. Следовательно, если не весь код в приложении видит
То же определение черты, ODR нарушается.
C ++ 11 std::allocator_traits
позволяет избежать этих ловушек, поскольку все контейнеры STL могут извлекать свойства только из своих Allocator
политика через std::allocator_traits<Allocator>
, Если пользователи решат не предоставлять или забыть предоставить некоторые обязательные элементы политики, класс признаков может вмешаться и предоставить значения по умолчанию для этих пропущенных элементов. Так как allocator_traits
сам по себе не может быть специализированным, пользователи всегда должны проходить полностью определенную политику выделения ресурсов, чтобы настроить распределение памяти в своих контейнерах, и не может произойти никаких нарушений ODR.
Обратите внимание, что, как писатель библиотеки, можно по-прежнему специализировать шаблоны классов признаков (как это делает STL в iterator_traits<T*>
), но хорошей практикой является передача всех пользовательских специализаций через классы политики в многозначные признаки, которые могут извлекать специализированное поведение (как это делает STL в allocator_traits<A>
).
ОБНОВИТЬ: Проблемы ODR пользовательских специализаций классов признаков возникают, главным образом, когда признаки используются как глобальные шаблоны классов и вы не можете гарантировать, что все будущие пользователи будут видеть все другие пользовательские специализации. Политики локальные параметры шаблона и содержат все соответствующие определения, позволяющие определять их пользователем без вмешательства в другой код. Локальные параметры шаблона, которые содержат только тип и константы — но без поведенческих функций — могут все еще называться «чертами», но они не будут видны для другого кода, такого как std::iterator_traits
а также std::allocator_traits
,
Я думаю, что вы найдете лучший ответ на свой вопрос в эта книга Андрея Александреску. Здесь я постараюсь дать только краткий обзор. Надеюсь, это поможет.
класс черт это класс, который обычно предназначен для мета-функции, связывающей типы с другими типами или с постоянными значениями, чтобы обеспечить характеристику этих типов. Другими словами, это способ моделирования свойства типов. Механизм обычно использует шаблоны и специализацию шаблонов для определения ассоциации:
template<typename T>
struct my_trait
{
typedef T& reference_type;
static const bool isReference = false;
// ... (possibly more properties here)
};
template<>
struct my_trait<T&>
{
typedef T& reference_type;
static const bool isReference = true;
// ... (possibly more properties here)
};
Метафункция черты my_trait<>
выше связывает ссылочный тип T&
и постоянное логическое значение false
для всех типов T
которые не сами ссылки; с другой стороны, он связывает ссылочный тип T&
и постоянное логическое значение true
для всех типов T
тот являются Рекомендации.
Так, например:
int -> reference_type = int&
isReference = false
int& -> reference_type = int&
isReference = true
В коде мы могли бы утверждать вышеизложенное следующим образом (все четыре строки ниже будут скомпилированы, что означает, что условие, выраженное в первом аргументе для static_assert()
доволен)
static_assert(!(my_trait<int>::isReference), "Error!");
static_assert( my_trait<int&>::isReference, "Error!");
static_assert(
std::is_same<typename my_trait<int>::reference_type, int&>::value,
"Error!");
static_assert(
std::is_same<typename my_trait<int&>::reference_type, int&>::value,
"Err!");
Здесь вы могли видеть, что я использовал стандарт std::is_same<>
шаблон, который сам по себе является мета-функцией, которая принимает два, вместо одного введите аргумент. Здесь все может быть сколь угодно сложно.
Хотя std::is_same<>
является частью type_traits
заголовок, некоторые считают, что шаблон класса является классом черт типа, только если он действует как мета-предикат (таким образом, принимая один параметр шаблона). Однако, насколько мне известно, терминология четко не определена.
Для примера использования класса признаков в стандартной библиотеке C ++ посмотрите, как спроектированы библиотека ввода / вывода и библиотека строк.
политика это что-то немного другое (на самом деле, совсем другое). Обычно подразумевается, что это класс, который определяет поведение другого универсального класса в отношении определенных операций, которые потенциально могут быть реализованы несколькими различными способами (и, следовательно, реализация которых оставлена на усмотрение класса политики).
Например, универсальный класс интеллектуальных указателей может быть спроектирован как шаблонный класс, который принимает политику в качестве параметра шаблона для принятия решения о том, как обрабатывать повторный подсчет — это всего лишь гипотетический, слишком упрощенный и иллюстративный пример, поэтому, пожалуйста, попробуйте абстрагироваться из этого конкретного кода и сосредоточиться на механизм.
Это позволило бы разработчику интеллектуального указателя не делать жестко заданного обязательства относительно того, должны ли модификации счетчика ссылок выполняться поточно-ориентированным образом:
template<typename T, typename P>
class smart_ptr : protected P
{
public:
// ...
smart_ptr(smart_ptr const& sp)
:
p(sp.p),
refcount(sp.refcount)
{
P::add_ref(refcount);
}
// ...
private:
T* p;
int* refcount;
};
В многопоточном контексте клиент может использовать создание шаблона смарт-указателя с политикой, которая реализует поточно-ориентированные приращения и уменьшения счетчика ссылок (предполагается, что Windows Platformmed):
class mt_refcount_policy
{
protected:
add_ref(int* refcount) { ::InterlockedIncrement(refcount); }
release(int* refcount) { ::InterlockedDecrement(refcount); }
};
template<typename T>
using my_smart_ptr = smart_ptr<T, mt_refcount_policy>;
С другой стороны, в однопоточной среде клиент может создать экземпляр шаблона смарт-указателя с классом политики, который просто увеличивает и уменьшает значение счетчика:
class st_refcount_policy
{
protected:
add_ref(int* refcount) { (*refcount)++; }
release(int* refcount) { (*refcount)--; }
};
template<typename T>
using my_smart_ptr = smart_ptr<T, st_refcount_policy>;
Таким образом, разработчик библиотеки предоставил гибкое решение, способное предложить лучший компромисс между производительностью и безопасностью («Вы не платите за то, что не используете»).
Если вы используете ModeT, IsReentrant и IsAsync для управления поведением Сервера, тогда это политика.
В качестве альтернативы, если вы хотите описать характеристики сервера для другого объекта, вы можете определить класс признаков следующим образом:
template <typename ServerType>
class ServerTraits;
template<>
class ServerTraits<Server>
{
enum { ModeT = SomeNamespace::MODE_NORMAL };
static const bool IsReentrant = true;
static const bool IsAsync = true;
}
Вот несколько примеров, поясняющих комментарий Алекса Чемберлена:
Типичным примером класса признаков является std :: iterator_traits. Допустим, у нас есть некоторый шаблонный класс C с функцией-членом, который принимает два итератора, перебирает значения и каким-то образом накапливает результат. Мы хотим, чтобы стратегия накопления также была определена как часть шаблона, но для ее достижения мы будем использовать политику, а не черту.
template <typename Iterator, typename AccumulationPolicy>
class C{
void foo(Iterator begin, Iterator end){
AccumulationPolicy::Accumulator accumulator;
for(Iterator i = begin; i != end; ++i){
std::iterator_traits<Iterator>::value_type value = *i;
accumulator.add(value);
}
}
};
Политика передается в наш шаблонный класс, в то время как признак является производным от параметра шаблона. То, что у вас есть, больше похоже на политику. Есть ситуации, когда черты являются более подходящими, и где политика является более подходящей, и часто тот же самый эффект может быть достигнут с любым из методов, приводящих к некоторой дискуссии, которая является наиболее выразительной.