Подтвердить что ты не робот

В чем разница между чертой и политикой?

У меня есть класс, поведение которого я пытаюсь настроить.

template<int ModeT, bool IsAsync, bool IsReentrant> ServerTraits;

Затем позже у меня есть свой серверный объект:

template<typename TraitsT>
class Server {...};

Мой вопрос в том, что мое использование выше - это мое именование неправильно? Является ли мой шаблонный параметр фактически политикой, а не чертой?

Когда шаблонный аргумент отличается от политики?

4b9b3361

Ответ 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);
        }
    }
};

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