В чем разница между чертой и политикой?
У меня есть класс, поведение которого я пытаюсь настроить.
template<int ModeT, bool IsAsync, bool IsReentrant> ServerTraits;
Затем позже у меня есть свой серверный объект:
template<typename TraitsT>
class Server {...};
Мой вопрос в том, что мое использование выше - это мое именование неправильно? Является ли мой шаблонный параметр фактически политикой, а не чертой?
Когда шаблонный аргумент отличается от политики?
Ответы
Ответ 1
Политика
Политики - это классы (или шаблоны классов) для приведения поведения в родительский класс, как правило, через наследование. Разлагая родительский интерфейс в ортогональные (независимые) измерения, классы политик образуют строительные блоки более сложных интерфейсов. Часто просматриваемый шаблон заключается в предоставлении политик в качестве определяемых пользователем шаблонов (или шаблонных шаблонов) с предоставленным библиотекой значением по умолчанию. Пример из стандартной библиотеки - это Allocators, которые являются параметрами шаблона политики для всех контейнеров 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
).
Взаимодействие между политиками и чертами
При написании собственных общих библиотек важно подумать о том, как пользователи могут специализировать свои собственные шаблоны классов. Однако нужно быть осторожным, чтобы пользователи не стали жертвой правила One Definition Rule, используя специализации признаков для инъекций, а не для извлечения поведения. Перефразировать этот старый пост Андрея Александреску
Основная проблема заключается в том, что код, который не видит специализированного версия признака все еще будет компилироваться, скорее всего, будет ссылка, и иногда может даже работать. Это связано с тем, что в отсутствие явная специализация, неспециализированный шаблон запускается, вероятно, реализуя общее поведение, которое работает для вашего частного случая как Что ж. Следовательно, если не весь код в приложении видит то же определение признака, ODR нарушается.
С++ 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
.
Ответ 2
Думаю, вы найдете наилучший ответ на свой вопрос в этой книге Андрея Александреску. Здесь я попытаюсь дать краткий обзор. Надеюсь, это поможет.
Класс признаков - это класс, который обычно предназначен для мета-функции, связывающей типы с другими типами или с постоянными значениями, чтобы обеспечить характеристику этих типов. Другими словами, это способ моделирования свойств типов. Механизм обычно использует шаблоны и специализированную специализацию для определения ассоциации:
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
, некоторые рассматривают шаблон класса как класс признаков типов только в том случае, если он действует как мета-предикат (таким образом, принимая один параметр шаблона). Однако, насколько мне известно, терминология четко не определена.
Для примера использования класса признаков в стандартной библиотеке С++ рассмотрите, как создаются библиотека ввода/вывода и строковая библиотека.
A политика - это нечто немного отличающееся (на самом деле, совсем другое). Обычно это класс, который указывает, что поведение другого родового класса должно быть связано с определенными операциями, которые потенциально могут быть реализованы несколькими способами (и реализация которых, следовательно, оставлена до класса политики).
Например, общий класс интеллектуальных указателей может быть спроектирован как класс шаблона, который принимает политику в качестве параметра шаблона для принятия решения о том, как обрабатывать подсчет ссылок - это просто гипотетический, чрезмерно упрощенный и иллюстративный пример, поэтому, пожалуйста, попытайтесь отвлечься от этого конкретного кода и сосредоточьтесь на механизме.
Это позволило бы разработчику интеллектуального указателя не делать жестко закодированного обязательства относительно того, должны ли изменения эталонного счетчика выполняться поточно-безопасным способом:
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):
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>;
Таким образом, разработчик библиотеки предоставил гибкое решение, способное предложить лучший компромисс между производительностью и безопасностью ( "Вы не платите за то, что не используете" ).
Ответ 3
Если вы используете 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;
}
Ответ 4
Вот несколько примеров, чтобы прояснить комментарий Алекса Чемберлена:
Общим примером класса признаков является 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);
}
}
};
Политика передается в наш шаблонный класс, а признак - из параметра шаблона. Так что у вас есть более сродни политике. Есть ситуации, когда признаки более уместны и где политика более уместна, и часто один и тот же эффект может быть достигнут с помощью любого метода, приводящего к некоторым дебатам о том, что является наиболее выразительным.