politico politica personalidad lider definicion c++ design template-meta-programming typetraits policy-based-design

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



personalidad de un politico (4)

Tengo una clase cuyo comportamiento estoy tratando de configurar.

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

Luego, más tarde, tengo mi servidor en sí mismo:

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

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

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


Políticas

Las políticas son clases (o plantillas de clases) para inyectar el comportamiento en una clase principal, generalmente a través de la 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 que se ve a menudo es el de proporcionar políticas como parámetros de plantilla (o plantilla-plantilla) definibles por el usuario con un valor predeterminado proporcionado por la biblioteca. Un ejemplo de la Biblioteca estándar son los Asignadores, que son parámetros de plantilla de política de todos los contenedores STL

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

Aquí, el parámetro de la plantilla Allocator (que a su vez también es 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 estándar std::allocator<T> .

Como es típico en el polimorfismo basado en plantillas, los requisitos de interfaz en las clases de políticas son implícitos y semánticos (basados ​​en expresiones válidas) más que explícitos y sintácticos (basados ​​en la definición de funciones de miembros virtuales).

Tenga en cuenta que los contenedores asociativos no ordenados más recientes tienen más de una política. Además del parámetro de plantilla Allocator habitual, también adoptan una política Hash que se predetermina a std::hash<Key> function object. Esto permite a los usuarios de contenedores no ordenados 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 un solo valor y rasgos de múltiples valores. Ejemplos de rasgos de valor único 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 valor único se utilizan a menudo en metaprogramación de plantillas y trucos SFINAE para sobrecargar una plantilla de función en función de una condición de tipo.

Ejemplos de rasgos multivalor son los iterator_traits y allocator_traits de los encabezados <iterator> y <memory> , respectivamente. Como 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 std::iterator_traits<T>::value_type códigos genéricos para clases de iteradores completas incluso para punteros crudos (ya que los punteros crudos no tienen un miembro value_type ).

Interacción entre políticas y rasgos

Al escribir sus propias bibliotecas genéricas, es importante pensar en las formas en que los usuarios pueden especializar sus propias plantillas de clase. Sin embargo, hay que tener cuidado de no dejar que los usuarios sean víctimas de la regla de una sola definición mediante el uso de especializaciones de rasgos para inyectar en lugar de extraer el comportamiento. Parafraseando este viejo post de Andrei Alexandrescu

El problema fundamental es que el código que no vea la versión especializada de un rasgo se compilará, probablemente se vincule y, a veces, incluso se ejecute. Esto se debe a que, en ausencia de una especialización explícita, la plantilla no especializada se activa, probablemente implementando un comportamiento genérico que también funciona para su caso especial. En consecuencia, si no todo el código en una aplicación ve la misma definición de rasgo, se infringe el ODR.

El C ++ 11 std::allocator_traits evita estas trampas al exigir que todos los contenedores STL solo puedan extraer propiedades de sus políticas de Allocator mediante std::allocator_traits<Allocator> . Si los usuarios eligen no olvidar u olvidarse de proporcionar algunos de los miembros de la política necesarios, la clase de rasgos puede intervenir y proporcionar valores predeterminados para los miembros que faltan. Debido a que allocator_traits no puede ser especializado, los usuarios siempre tienen que aprobar una política de asignadores completamente definida para personalizar la asignación de memoria de sus contenedores, y no pueden ocurrir violaciones silenciosas de ODR.

Tenga en cuenta que, como escritor de bibliotecas, todavía se pueden especializar las plantillas de clases de rasgos (como lo hace STL en iterator_traits<T*> ), pero es una buena práctica pasar todas las especializaciones definidas por el usuario a clases de políticas en rasgos de múltiples valores 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 clases de rasgos ocurren principalmente cuando los rasgos se utilizan como plantillas de clase globales y no se puede garantizar que todos los usuarios futuros verán todas las demás especializaciones definidas por el usuario. Las políticas son parámetros de plantillas locales y contienen todas las definiciones relevantes, lo que les permite ser definidas por el usuario sin interferencia en otro código. Los parámetros de plantillas locales que solo contienen tipos y constantes -pero no funciones de comportamiento- todavía se pueden llamar "rasgos", pero no serían visibles para otros códigos como std::iterator_traits y std::allocator_traits .


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

Un ejemplo común de una clase de rasgo es std :: iterator_traits. Digamos que tenemos alguna clase de plantilla 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 utilizaremos 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 transfiere a nuestra clase de plantilla, mientras que la característica se deriva del parámetro de la plantilla. Entonces, lo que tienes es más parecido a una política. Hay situaciones en las que los rasgos son más apropiados y en los que las políticas son más apropiadas, y con frecuencia se puede lograr el mismo efecto con cualquiera de los métodos que conduzca a un debate sobre cuál es el más expresivo.


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

Una clase de rasgos es una clase que, por lo general, pretende ser una metafunción que asocie tipos a otros tipos o valores constantes para proporcionar una caracterización de esos tipos. En otras palabras, es una forma de modelar las propiedades de los tipos . El mecanismo normalmente aprovecha 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 rasgos my_trait my_trait<> arriba asocia el tipo de referencia T& y el valor booleano constante false a todos los tipos T que no son referencias; por otro lado, asocia el tipo de referencia T& y el valor booleano constante true a todos los tipos T que son referencias.

Entonces, por ejemplo:

int -> reference_type = int& isReference = false int& -> reference_type = int& isReference = true

En el código, podríamos afirmar lo anterior de la siguiente manera (se compilarán las cuatro líneas siguientes, lo que significa que se cumple la condición expresada en el primer argumento para 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!" );

Aquí podría ver que hice uso de la plantilla estándar std::is_same<> , que a su vez es una std::is_same<> que acepta dos , en lugar de uno, tipo de argumento. 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, acepta un parámetro de plantilla). Hasta donde yo sé, sin embargo, la terminología no está claramente definida.

Para ver un ejemplo del uso de una clase de rasgos en la Biblioteca estándar de C ++, eche un vistazo a cómo están diseñados la biblioteca de entrada / salida y la biblioteca de cadenas.

Una política es algo ligeramente diferente (en realidad, bastante diferente). Por lo general, se supone que es una clase que especifica cuál debe ser el comportamiento de otra clase genérica con respecto a ciertas operaciones que podrían realizarse potencialmente de varias maneras diferentes (y cuya implementación, por lo tanto, queda a la altura de la clase de política).

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

Eso le permitiría al diseñador del puntero inteligente no hacer un compromiso codificado en cuanto a si las modificaciones del contador de referencia se realizarán o no de manera segura.

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 creación de instancias de la plantilla de puntero inteligente con una política que realice incrementos y disminuciones seguras de subprocesos del contador de referencia (plataformas de Windows asumidas 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 de 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 forma, el diseñador de la biblioteca ha proporcionado una solución flexible que es capaz de ofrecer el mejor compromiso entre rendimiento y seguridad ( "No paga por lo que no usa" ).


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

Alternativamente, si quieres una forma de describir las características del servidor a otro objeto, entonces podrías definir una clase de rasgos 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; }