¿Cuál es la diferencia entre un rasgo y una política?


Tengo una clase cuyo comportamiento estoy tratando de configurar.

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

Luego más tarde tengo mi objeto servidor en sí:

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

Mi pregunta es para mi uso anterior ¿está mal mi nombre? ¿Es mi parámetro de plantilla realmente una política en lugar de un rasgo?

¿Cuándo es un argumento de plantilla un rasgo frente a una política?

Author: TemplateRex, 2013-02-06

4 answers

Políticas

Las directivas son clases (o plantillas de clase) para inyectar el comportamiento en una clase padre, normalmente a través de herencia. Al descomponer una interfaz padre en dimensiones ortogonales (independientes), las clases de políticas forman los bloques de construcción de interfaces más complejas. Un patrón a menudo visto es proporcionar políticas como parámetros de plantilla (o plantilla-plantilla) definibles por el usuario con un valor predeterminado suministrado por la biblioteca. Un ejemplo de la Biblioteca Estándar son los Asignadores, que son parámetros de plantilla de directiva de todos los contenedores STL

template<class T, class Allocator = std::allocator<T>> class vector;

Aquí, el parámetro de plantilla Allocator (que a su vez es también una plantilla de clase!) inyecta la política de asignación y desasignación de memoria en la clase principal std::vector. Si el usuario no proporciona un asignador, se utiliza el valor predeterminado std::allocator<T>.

Como es típico en el polimporfismo basado en plantillas, los requisitos de interfaz en las clases de políticas son implícitos y semánticos (basados en expresiones válidas) en lugar de explícito y sintáctico (basado en la definición de funciones miembro virtuales).

Tenga en cuenta que los contenedores asociativos desordenados más recientes tienen más de una política. Además del parámetro habitual de la plantilla Allocator, también toman una política Hash que por defecto es std::hash<Key> objeto de función. Esto permite a los usuarios de contenedores desordenados configurarlos a lo largo de múltiples dimensiones ortogonales (asignación de memoria y hash).

Rasgos

Los rasgos son plantillas de clase para extraer propiedades de un tipo genérico. Hay dos tipos de rasgos: rasgos de valor único y rasgos de valor múltiple. Ejemplos de rasgos de un solo valor son los del encabezado <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;
};

Los rasgos de un solo valor se usan a menudo en metaprogramación de plantillas y trucos SFINAE para sobrecargar una plantilla de función basada en una condición de tipo.

Ejemplos de rasgos multi-valorados son los iterator_traits y allocator_traits de las cabeceras <iterator> y <memory>, respectivamente. Dado que los rasgos son plantillas de clase, pueden ser especializados. A continuación un ejemplo de la especialización de iterator_traits para 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;
};

La expresión std::iterator_traits<T>::value_type hace posible hacer que el código genérico para clases iteradoras completas sea utilizable incluso para punteros raw (ya que los punteros raw no tienen un miembro value_type).

Interacción entre políticas y rasgos

Al escribir sus propias bibliotecas genéricas, es importante pensar en formas en que los usuarios pueden especializarse en las suyas plantillas de clase. Sin embargo, hay que tener cuidado de no dejar que los usuarios caigan víctimas de la Regla de una Definición mediante el uso de especializaciones de rasgos para inyectar en lugar de extraer el comportamiento. Para parafrasear esto antiguo post por Andrei Alexandrescu

El problema fundamental es que el código que no ve el especializado versión de un rasgo todavía se compilará, es probable que enlace, y a veces incluso podría correr. Esto se debe a que en ausencia de la expresa especialización, la plantilla no especializada entra en acción, probablemente implementar un comportamiento genérico que funcione para su caso especial como bien. En consecuencia, si no todo el código en una aplicación ve la la misma definición de un rasgo, el ODR es violado.

El C++11 std::allocator_traits evita estas trampas al imponer que todos los contenedores STL solo pueden extraer propiedades de sus políticas Allocator a través de std::allocator_traits<Allocator>. Si los usuarios optan por no u olvidarse de proporcionar algunos de los miembros de la política requeridos, la clase traits puede intervenir y proporcionar valores predeterminados para los miembros que faltan. Debido a que allocator_traits en sí no puede ser especializado, los usuarios siempre tienen que pasar una política de asignador completamente definida para personalizar la asignación de memoria de sus contenedores, y no pueden ocurrir violaciones ODR silenciosas.

Tenga en cuenta que como escritor de bibliotecas, todavía se pueden especializar las plantillas de clase traits (como lo hace la STL en iterator_traits<T*>), pero es una buena práctica pasar todas las especializaciones definidas por el usuario a través de las clases de rasgos multi-valorados que pueden extraer el comportamiento especializado (como lo hace el STL en allocator_traits<A>).

ACTUALIZACIÓN: Los problemas ODR de las especializaciones definidas por el usuario de las clases traits ocurren principalmente cuando los traits se utilizan como plantillas de clase globales y no se puede garantizar que todos los usuarios futuros vean todas las demás especializaciones definidas por el usuario. Las políticas son parámetros de plantilla local y contienen todas las definiciones relevantes, lo que les permite ser definidos por el usuario sin interferencia en otro código. Los parámetros de plantilla locales que solo contienen tipo y constantes, pero no funciones conductuales, podrían llamarse "rasgos", pero no serían visibles para otro código como std::iterator_traits y std::allocator_traits.

 79
Author: TemplateRex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-08 15:27:03

Creo que encontrará la mejor respuesta posible a su pregunta en este libro de Andrei Alexandrescu. Aquí, trataré de dar un breve resumen. Espero que ayude.


Una clase de rasgos es una clase que generalmente pretende ser una meta-función que asocia tipos a otros tipos o a valores constantes para proporcionar una caracterización de esos tipos. En otras palabras, es una forma de modelar propiedades de los tipos. Mecanismo normalmente explota las plantillas y la especialización de plantillas para definir la asociación:

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

La metafunción de rasgo my_trait<> anterior asocia el tipo de referencia T& y el valor booleano constante false a todos los tipos T que son no referencias; por otro lado, asocia el tipo de referencia T& y el valor booleano constante true a todos los tipos T que son referencias.

Así que por ejemplo:

int  -> reference_type = int&
        isReference = false

int& -> reference_type = int&
        isReference = true

En código, podríamos haga valer lo anterior de la siguiente manera (las cuatro líneas a continuación se compilarán, lo que significa que la condición expresada en el primer argumento de static_assert() se cumple):

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!"
    );

Aquí se puede ver que hice uso de la plantilla estándar std::is_same<>, que es en sí misma una meta-función que acepta dos, en lugar de uno, argumento de tipo. Las cosas pueden complicarse arbitrariamente aquí.

Aunque std::is_same<> es parte del encabezado type_traits, algunos consideran que una plantilla de clase es una clase de rasgos de tipo solo si actúa como un meta-predicado (por lo tanto, aceptando un parámetro de plantilla). Sin embargo, a mi leal saber y entender, la terminología no está claramente definida.

Para ver un ejemplo de uso de una clase traits en la Biblioteca Estándar de C++, eche un vistazo a cómo están diseñadas la Biblioteca de Entrada/Salida y la Biblioteca de Cadenas.


Una política es algo ligeramente diferente (en realidad, bastante diferente). Normalmente está destinado a ser una clase que especifica lo que el comportamiento de otra clase genérica debe referirse a ciertas operaciones que podrían realizarse potencialmente de varias maneras diferentes (y cuya implementación, por lo tanto, se deja en manos de la clase policy).

Por ejemplo, una clase genérica de puntero inteligente podría diseñarse como una clase de plantilla que acepta una política como un parámetro de plantilla para decidir cómo manejar el recuento de referencias, esto es solo un ejemplo hipotético, excesivamente simplista e ilustrativo, así que intente abstraerse de este concreto codificar y centrarse en el mecanismo .

Eso permitiría al diseñador del puntero inteligente no hacer ningún compromiso codificado en cuanto a si las modificaciones del contador de referencia se realizarán de manera segura para subprocesos:

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

En un contexto de subprocesos múltiples, un cliente podría usar una instanciación de la plantilla de puntero inteligente con una política que realiza incrementos y decrementos seguros para subprocesos del contador de referencia (se asume que Windows platformed aquí):

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

En un entorno de subproceso único, por otro lado, un cliente podría instanciar la plantilla de puntero inteligente con una clase de política que simplemente aumenta y disminuye el valor del contador:

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

De esta manera, el diseñador de bibliotecas ha proporcionado una solución flexible que es capaz de ofrecer el mejor compromiso entre rendimiento y seguridad ("No pagas por lo que no usas").

 22
Author: Andy Prowl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-06 09:28:30

Si está utilizando ModeT, IsReentrant e IsAsync para controlar el comportamiento del servidor, entonces es una política.

Alternativamente, si desea una forma de describir las características del servidor a otro objeto, entonces podría definir una clase traits como esta:

template <typename ServerType>
class ServerTraits;

template<>
class ServerTraits<Server>
{
    enum { ModeT = SomeNamespace::MODE_NORMAL };
    static const bool IsReentrant = true;
    static const bool IsAsync = true;
}
 3
Author: Twisted Oracle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-17 23:11:20

Aquí hay un par de ejemplos para aclarar el comentario de Alex Chamberlain:

Un ejemplo común de clase trait es std::iterator_traits. Digamos que tenemos una plantilla clase C con una función miembro que toma dos iteradores, itera sobre los valores y acumula el resultado de alguna manera. Queremos que la estrategia de acumulación se defina también como parte de la plantilla, pero usaremos una política en lugar de un rasgo para lograrlo.

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

La política se pasa a nuestra plantilla clase, mientras que el rasgo se deriva del parámetro template. Así que lo que tienes es más parecido a una política. Hay situaciones donde los rasgos son más apropiados, y donde las políticas son más apropiadas, y a menudo el mismo efecto se puede lograr con cualquiera de los métodos que conducen a un debate sobre cuál es el más expresivo.

 1
Author: jmetcalfe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-04 09:44:57