33 votos

¿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 propio objeto server:

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

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

¿Cuando es un argumento basado en plantilla un rasgo versus una política?

42voto

TemplateRex Puntos 26447

Las políticas de

Las políticas son las clases (o de plantillas de clase) para inyectar el comportamiento en una clase padre, normalmente a través de la herencia. A través de la descomposición de un padre de la interfaz en ortogonales (independientes) dimensiones, la política de las clases forman los bloques de construcción de la más compleja de las interfaces. A menudo visto patrón es el suministro de políticas definidos por el usuario de la plantilla (o template plantilla) de los parámetros de una biblioteca suministrado por defecto. Un ejemplo de la Biblioteca Estándar son los Asignadores, que son la política de los parámetros de la plantilla de todos los contenedores STL

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

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

Como es típico en la plantilla basada en polymporphism, los requisitos de la interfaz en la política de las clases son implícitas y semántica (basado en expresiones válidas) más que explícita y sintáctico (basado en la definición de las funciones miembro virtuales).

Tenga en cuenta que la más reciente desordenada asociativa contenedores, tienen más de una póliza. Además de los habituales Allocator parámetro de plantilla, también llevan un Hash política que por defecto std::hash<Key> objeto de la función. Esto permite a los usuarios de desordenada de contenedores para configurar a lo largo de múltiples dimensiones ortogonales (asignación de memoria y de hash).

Rasgos

Las características de las plantillas de clase para extraer propiedades de un tipo genérico. Hay dos tipos de rasgos: valor único de rasgos y valores múltiples rasgos. Ejemplos de valor único rasgos son los de la cabecera <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;
};

Valor único de rasgos a menudo son utilizados en la plantilla de la metaprogramación y SFINAE trucos a la sobrecarga de una plantilla de función, basada en un tipo de condición.

Ejemplos de los múltiples valores de los rasgos de la iterator_traits y allocator_traits de los encabezados <iterator> y <memory>, respectivamente. Dado que las características de las plantillas de clase, que puede ser especializado. 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 que sea posible para hacer que el código genérico de pleno derecho iterador clases útil incluso para raw punteros (desde raw punteros no tiene un miembro, value_type).

La interacción entre las políticas y los rasgos

Cuando la escritura de su propia genérico de las bibliotecas, es importante pensar sobre las maneras en que los usuarios pueden especializar sus propias plantillas de clase. Uno tiene que tener cuidado, sin embargo, no permiten a los usuarios ser víctima de Una Definición de la Regla mediante el uso de las especializaciones de los rasgos de inyectar en lugar de extracto de comportamiento. Parafraseando a este viejo post por Andrei Alexandrescu

El problema fundamental es que el código que no ve a la especializada versión de un rasgo todavía compilar, es probable que el enlace, y algunas veces puede incluso ejecutar. Esto es debido a que en la ausencia de la explícita de la especialización, las personas no especializadas de la plantilla activa, las probabilidades de la implementación de un comportamiento genérico que funciona para su caso especial, ya que bien. En consecuencia, si no todo el código en una aplicación se ve el la definición misma de un rasgo, el de la ODR es violado.

El C++11 std::allocator_traits evita estos problemas mediante la aplicación de que todos los contenedores STL solo puede extraer las propiedades de sus Allocator políticas a través de la std::allocator_traits<Allocator>. Si los usuarios no deseen o se olvida de suministro de parte de la necesaria política de los miembros, la clase de rasgos puede intervenir y suministro de los valores por defecto para los miembros que faltan. Porque allocator_traits sí no puede ser especializados, los usuarios siempre tienen que pasar por un completamente definido asignador de política con el fin de personalizar sus envases de asignación de memoria, y no en silencio ODR violaciones pueden ocurrir.

Tenga en cuenta que como una biblioteca-escritor, uno se puede especializar los rasgos de plantillas de clase (como la STL en iterator_traits<T*>), pero es una buena práctica para pasar todos definido por el usuario especializaciones a través de la política de clases en varios valores de los rasgos que puede extraer los especializados en el comportamiento (como la STL en allocator_traits<A>).

ACTUALIZACIÓN: El ODR problemas definidos por el usuario de las especializaciones de los rasgos de las clases ocurren principalmente cuando los rasgos son utilizados como global plantillas de clase y usted no puede garantizar que en el futuro todos los usuarios podrán ver todos los otros definidos por el usuario especializaciones. Políticas locales de los parámetros de la plantilla y contienen todas las definiciones pertinentes, lo que les permite ser definidos por el usuario sin la interferencia en el otro código. Local de los parámetros de la plantilla que sólo contienen el tipo y constantes, pero no en cuanto al comportamiento de las funciones, todavía podría ser llamado "rasgos", pero que no sería visible a otros códigos como el std::iterator_traits y std::allocator_traits.

9voto

Andy Prowl Puntos 62121

Creo que va a encontrar la mejor respuesta posible a su pregunta en este libro por Andrei Alexandrescu. Aquí, voy a tratar de dar una breve descripción. Espero que ayude.


Una de los rasgos de la clase es la clase que pretende ser un meta-función de asociar los tipos de otros tipos, o a valores constantes para proporcionar una caracterización de los tipos. En otras palabras, es una forma de modelar las propiedades de los tipos. El mecanismo normalmente explota las plantillas y especialización 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)
};

El rasgo metafunction my_trait<> por encima asocia el tipo de referencia T& y el constante valor Booleano false a todos los tipos T que son no sí mismas referencias; por otro lado, asocia el tipo de referencia T& y el constante valor Booleano true a todos los tipos T que son las referencias.

Así, por ejemplo:

int  -> reference_type = int&
        isReference = false

int& -> reference_type = int&
        isReference = true

En el código, se podría afirmar que el anterior de la siguiente manera (todas las cuatro líneas de abajo va a compilar, lo que significa que la condición expresada en el primer argumento a static_assert() está satisfecho):

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 he hecho uso de la norma std::is_same<> de la plantilla, que es en sí mismo una meta-función que acepta dos, en lugar de uno, el tipo de argumento. Las cosas pueden ser arbitrariamente complicado aquí.

Aunque std::is_same<> es parte de la type_traits de encabezado, que algunos consideran una plantilla de clase a ser un tipo de clase de rasgos sólo si actúa como un meta-predicado (por lo tanto, la aceptación de un parámetro de plantilla). Al mejor de mi conocimiento, sin embargo, la terminología no está claramente definido.

Para un ejemplo de uso de una clase de rasgos en el Estándar de C++ Biblioteca, echar un vistazo a cómo la Entrada/Salida de la Biblioteca y de la Cadena de la Biblioteca están diseñados.


Una política es algo ligeramente diferente (en realidad, bastante diferentes). Normalmente está destinado a ser una clase que especifica lo que el comportamiento de otra, la clase genérica debe ser con respecto a ciertas operaciones que podrían ser potencialmente di cuenta de varias maneras diferentes (y cuya puesta en práctica es, por lo tanto, deja a la política de la clase).

Por ejemplo, un genérico puntero inteligente de la clase puede ser diseñado como una clase de plantilla que acepta una política como un parámetro de plantilla para decidir cómo manejar ref-contar - esto es sólo una hipótesis, excesivamente simplista, y ejemplo ilustrativo, así que por favor trate de resumen de este código concreto y se centran en el mecanismo.

Que permita al diseñador del puntero inteligente para hacer que no codificado compromiso en cuanto a si o no modificaciones en el contador de referencia se llevará a cabo de una 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 multi-threaded contexto, un cliente podría usar una instancia de puntero inteligente de la plantilla con una política que da cuenta de hilo de seguridad incrementa y decrementa el contador de referencia (Windows platformed supone 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 crear una instancia del puntero inteligente de la plantilla con una política de clase 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, la biblioteca diseñador ha proporcionado una solución flexible que es capaz de ofrecer el mejor compromiso entre rendimiento y seguridad ("no Se paga por lo que usted no use").

1voto

jmetcalfe Puntos 843

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

Un ejemplo común de un rasgo de la clase std::iterator_traits. Digamos que tenemos algunos de la plantilla de la clase C con un miembro de la función que toma dos iteradores, itera a través de los valores, y se acumula el resultado de alguna manera. Queremos que la acumulación de la estrategia se define como la parte de la plantilla también, pero el uso de una política en lugar de un rasgo para lograr eso.

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 de clase, mientras que el rasgo se deriva de los parámetros de una plantilla. Así que lo que tienes es más parecido a una política. Hay situaciones en las que los rasgos son los más adecuados, y donde las políticas son los más adecuados, y a menudo el mismo efecto se puede lograr ya sea con el método que conduce a un debate acerca de cuál es el más expresivo.

0voto

Twisted Oracle Puntos 26

Si usas ModeT, IsReentrant y IsAsync para controlar el comportamiento del servidor, entonces es una política.

Alternativamente, si usted es quiere una manera de describir las características del servidor a otro objeto, entonces usted podría definir una clase de rasgos como:

template <typename ServerType>
class ServerTraits;

template<>
class ServerTraits<Server>
{
    enum { ModeT = SomeNamespace::MODE_NORMAL };
    static const bool IsReentrant = true;
    static const bool IsAsync = true;
}

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X