Articulo de referencia

Diseño moderno de C++

Diseño moderno de C++: Programación genérica y patrones de diseño aplicados es un libro escrito por Andrei Alexandrescu , publicado en 2001 por Addison-Wesley . Ha sido consider...

Diseño moderno de C++: Programación genérica y patrones de diseño aplicados es un libro escrito por Andrei Alexandrescu , publicado en 2001 por Addison-Wesley . Ha sido considerado como "uno de los libros más importantes de C++" por Scott Meyers . [ 1 ]

El libro utiliza y explora una técnica de programación en C++ llamada metaprogramación con plantillas . Si bien Alexandrescu no inventó la técnica, la popularizó entre los programadores. Su libro contiene soluciones a problemas prácticos que pueden enfrentar los programadores de C++. Varias frases del libro se utilizan ahora en la comunidad de C++ como términos genéricos: C++ moderno (en contraposición al estilo C/C++), diseño basado en políticas y lista de tipos .

Todo el código descrito en el libro está disponible gratuitamente en su biblioteca Loki . El libro ha sido reeditado y traducido a varios idiomas desde 2001.

Diseño basado en políticas

El diseño basado en políticas , también conocido como diseño de clases basado en políticas o programación basada en políticas , es el término utilizado en Modern C++ Design para un enfoque de diseño basado en un modismo de C++ conocido como políticas . Se ha descrito como una variante en tiempo de compilación del patrón de estrategia y tiene conexiones con la metaprogramación de plantillas de C++ . Fue popularizado por primera vez en C++ por Andrei Alexandrescu con Modern C++ Design y con su columna Generic<Programming> en el C/C++ Users Journal , y actualmente está estrechamente asociado con C++ y D, ya que requiere un compilador con un soporte muy robusto para plantillas , lo cual no era común antes de 2003 aproximadamente.

Ejemplos anteriores de este enfoque de diseño, basado en código genérico parametrizado, incluyen módulos paramétricos (functores) de los lenguajes ML , [ 2 ] y asignadores de C++ para la política de gestión de memoria.

El concepto central en el diseño basado en políticas es una plantilla de clase (denominada clase anfitriona ), que recibe varios parámetros de tipo como entrada. Estos parámetros se instancian con tipos seleccionados por el usuario (denominados clases de política ), cada uno de los cuales implementa una interfaz implícita particular (denominada política ) y encapsula algún aspecto ortogonal (o mayormente ortogonal) del comportamiento de la clase anfitriona instanciada. Al proporcionar una clase anfitriona junto con un conjunto de implementaciones predefinidas para cada política, una biblioteca o módulo puede admitir un número exponencial de combinaciones de comportamiento diferentes, que se resuelven en tiempo de compilación y se seleccionan mediante la combinación de las distintas clases de política proporcionadas en la instanciación de la plantilla de la clase anfitriona. Además, al escribir una implementación personalizada de una política determinada, una biblioteca basada en políticas puede utilizarse en situaciones que requieren comportamientos imprevistos para quien implementa la biblioteca. Incluso en los casos en que solo se utilice una implementación de cada política, descomponer una clase en políticas puede facilitar el proceso de diseño, al aumentar la modularidad y resaltar con precisión dónde se han tomado decisiones de diseño ortogonales.

Si bien ensamblar componentes de software a partir de módulos intercambiables es un concepto poco realista, el diseño basado en políticas representa una innovación al aplicar dicho concepto al nivel (relativamente bajo) de definir el comportamiento de una clase individual. Las clases de políticas son similares a las funciones de devolución de llamada , pero se diferencian en que, en lugar de constar de una sola función , suelen contener varias funciones relacionadas ( métodos ), a menudo combinadas con variables de estado u otras funcionalidades como tipos anidados. Una clase anfitriona basada en políticas puede considerarse un tipo de metafunción , que recibe como entrada un conjunto de comportamientos representados por tipos y devuelve como salida un tipo que representa el resultado de combinar dichos comportamientos en un conjunto funcional. (A diferencia de las metafunciones de MPL , la salida suele estar representada por la propia clase anfitriona instanciada, en lugar de un tipo de salida anidado).

Una característica clave del paradigma de políticas es que, por lo general (aunque no es estrictamente necesario), la clase anfitriona derivará (se convertirá en una clase hija de) cada una de sus clases de políticas mediante herencia múltiple (pública) . (Las alternativas son que la clase anfitriona simplemente contenga una variable miembro de cada tipo de clase de política, o bien herede las clases de políticas de forma privada; sin embargo, heredar las clases de políticas públicamente tiene la gran ventaja de que una clase de política puede agregar nuevos métodos, heredados por la clase anfitriona instanciada y accesibles para sus usuarios, que la propia clase anfitriona ni siquiera necesita conocer). Una característica notable de este aspecto del paradigma de políticas es que, en relación con la programación orientada a objetos , las políticas invierten la relación entre la clase base y la clase derivada: mientras que en la POO las interfaces se representan tradicionalmente mediante clases base ( abstractas ) y las implementaciones de interfaces mediante clases derivadas, en el diseño basado en políticas la clase derivada (anfitriona) representa las interfaces y las clases base (de políticas) las implementan. En el caso de las políticas, la herencia pública no representa una relación de tipo "es un" entre la clase anfitriona y las clases de políticas. Si bien esto tradicionalmente se consideraría evidencia de un defecto de diseño en contextos de programación orientada a objetos, esto no se aplica en el contexto del lenguaje de políticas.

Una desventaja de las políticas en su encarnación actual es que la interfaz de la política no tiene una representación directa y explícita en el código , sino que se define implícitamente, mediante tipado dinámico , y debe documentarse por separado y manualmente, en comentarios . La idea principal es utilizar el análisis de variabilidad común para dividir el tipo en la implementación e interfaz fijas, la clase basada en políticas y las diferentes políticas. El truco está en saber qué va en la clase principal y qué políticas se deben crear. El artículo mencionado anteriormente da la siguiente respuesta: siempre que necesitemos tomar una posible decisión de diseño limitante, debemos posponer esa decisión, debemos delegarla a una política con el nombre apropiado.

Las clases de políticas pueden contener definiciones de implementación, tipos, etc. Básicamente, el diseñador de la clase plantilla principal definirá qué deben proporcionar las clases de políticas y qué puntos de personalización deben implementar.

Crear un buen conjunto de políticas, con la cantidad justa (por ejemplo, la mínima necesaria), puede ser una tarea delicada. Los distintos puntos de personalización, que están relacionados entre sí, deben agruparse en un único argumento de política, como por ejemplo, la política de almacenamiento, la política de validación, etc. Los diseñadores gráficos pueden nombrar sus políticas, las cuales representan conceptos, y no aquellas que representan operaciones o detalles menores de implementación.

El diseño basado en políticas puede incorporar otras técnicas útiles. Por ejemplo, el patrón de método plantilla se puede reinterpretar en tiempo de compilación, de modo que una clase principal tenga un algoritmo base que, en los puntos de personalización , llame a las funciones apropiadas de algunas de las políticas.  

Esto ahora se puede lograr dinámicamente mediante conceptos [ 3 ] desde C++20 .

Ejemplo sencillo

A continuación se presenta un ejemplo sencillo (artificial) de un programa "Hola mundo" en C++ , donde el texto a imprimir y el método para imprimirlo se descomponen mediante políticas. En este ejemplo, HelloWorldes una clase host que recibe dos políticas: una para especificar cómo se debe mostrar un mensaje y otra para el mensaje que se imprime. Tenga en cuenta que la implementación genérica está en runy, por lo tanto, el código no se puede compilar a menos que se proporcionen ambas políticas ( writey ).message

importar std ;usando std :: string ;plantilla < typename OutputPolicy , typename LanguagePolicy > clase HelloWorld : private OutputPolicy , private LanguagePolicy { public : // Método de comportamiento. void run () const { // Dos métodos de política. write ( message ()); } };clase WriteToStdout { protected : void write ( string && message ) const { std :: println ( "{}" , message ); } };clase EnglishMessage { protected : [[ nodiscard ]] string message () const noexcept { return "¡Hola, mundo!" ; } };clase GermanMessage { protected : [[ nodiscard ]] string message () const noexcept { return "¡Hola mundo!" ; } };int main () { // Ejemplo 1 HelloWorld < WriteToStdout , EnglishMessage > helloWorld ; helloWorld . run (); // Imprime "Hello, World!".// Ejemplo 2 // Hace lo mismo, pero usa otra política de idioma. HelloWorld < WriteToStdout , GermanMessage > helloWorld2 ; helloWorld2 . run (); // Imprime "Hallo Welt!". }

Los diseñadores pueden escribir fácilmente más OutputPolicys agregando nuevas clases con la función miembro writey tomándolas como nuevas OutputPolicys.

Biblioteca Loki

Loki es una biblioteca de software C++ escrita por Andrei Alexandrescu como parte de su libro Modern C++ Design .

La biblioteca hace un uso extensivo de la metaprogramación con plantillas de C++ e implementa varias herramientas de uso común: typelist , functor , singleton , smart pointer , object factory , visitor y multimethods .

Originalmente, la biblioteca solo era compatible con dos de los compiladores C++ más estándar ( CodeWarrior y Comeau C/C++ ): esfuerzos posteriores la hicieron utilizable con una amplia gama de compiladores (incluidos Visual C++ 6.0 , Borland C++ Builder 6.0 , Clang y GCC ). Los proveedores de compiladores utilizaron Loki como referencia de compatibilidad, lo que aumentó aún más el número de compiladores compatibles. [ 4 ]

El mantenimiento y el desarrollo de Loki se han mantenido gracias a una comunidad de código abierto liderada por Peter Kümmel y Richard Sposato como proyecto de SourceForge. Las contribuciones constantes de muchos usuarios han mejorado la robustez y la funcionalidad general de la biblioteca. Loki ya no está vinculado al libro, ya que cuenta con numerosos componentes nuevos (por ejemplo, StrongPtr, Printf y Scopeguard). Loki inspiró herramientas y funcionalidades similares que ahora también forman parte de la biblioteca Boost .

Véase también

Referencias

  1. Meyers, Scott (9 de agosto de 2006). "Los libros de C++ más importantes... de todos los tiempos" . Artima . Consultado el 8 de septiembre de 2025 .
  2. Capítulo 7. Tipos abstractos y functores cam.ac.uk
  3. Conceptos: El futuro de la programación genérica stroustrup.com
  4. C++ y más allá 2011: Sesión "Pregúntanos lo que quieras", http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2011-Scott-Andrei-and-Herb-Ask-Us-Anything en 51:40-51:51
  • Sitio web oficial , de Alexandrescu, conerratas
  • Consejos inteligentes , capítulo de muestra del libro
  • Loki en SourceForge
  • Código fuente original de la editorial del libro. Archivado el 2 de mayo de 2006 en la Wayback Machine.