Las tendencias modernas de desarrollo de C ++ sugieren el máximo rechazo posible de macros en el código. Pero a veces sin macros, y en su manifestación especialmente fea, uno no puede hacerlo, ya que sin ellos es aún peor. Sobre esto y la historia.
Como sabe, el primer paso para compilar C y C ++ es el preprocesador, que reemplaza las macros y las directivas del preprocesador con texto sin formato.
Esto nos permite hacer cosas extrañas, por ejemplo:
Después de que el preprocesador funciona, este malentendido se convertirá en el código correcto:
std::string str = "look, I'm a string!" ;
Por supuesto, este terrible encabezado no se puede incluir en ningún otro lado. Y sí, debido al hecho de que agregaremos este encabezado varias veces al mismo archivo, no #pragma una vez o incluiremos guardias.
En realidad, escribamos un ejemplo más complejo que haga diferentes cosas con la ayuda de macros y al mismo tiempo defienda contra #include aleatorio:
Esto todavía es feo, pero ya aparece un cierto encanto: cuando agrega un nuevo elemento a la clase enum, se agregará automáticamente a la declaración de salida sobrecargada.
Aquí puede formalizar el área de aplicación de este método: la necesidad de generar código en diferentes lugares en función de una fuente.
Y ahora la triste historia de X-Macro y Windows. Existe un sistema como Contadores de rendimiento de Windows, que le permite enviar ciertos contadores al sistema operativo para que otras aplicaciones puedan recogerlos. Por ejemplo, Zabbix se puede configurar para recopilar y monitorear cualquier contador de rendimiento. Esto es bastante conveniente, y no necesita reinventar la rueda con la devolución / consulta de datos.
Sinceramente pensé que agregar un nuevo contador se parece a la HANDLE counter = AddCounter ("name"). Ah, qué equivocado estaba.
Primero debe escribir un manifiesto XML especial (
ejemplo ), o generarlo usando el programa ecmangen.exe del SDK de Windows, pero por
alguna razón este ecmangen
se ha eliminado de las nuevas versiones del SDK de Windows 10. A continuación, debe generar el código y el archivo .rc utilizando la utilidad
ctrpp basada en nuestro manifiesto XML.
Agregar nuevos contadores al sistema en sí solo se realiza con la utilidad
lodctr con nuestro manifiesto XML en el argumento.
¿Que es un archivo .rc?Esta es una invención de Microsoft, no relacionada con el estándar C ++. Con estos archivos, puede incrustar recursos en exe \ dll, como cadenas \ iconos \ imágenes, etc., y luego recogerlos utilizando la API especial de Windows.
Los perfcounters usan estos archivos .rc para localizar los nombres de los contadores, y no está muy claro por qué estos nombres deben localizarse.
Resumiendo lo anterior: para agregar 1 contador necesitas:
- Cambiar manifiesto XML
- Genere nuevos archivos de proyecto .c y .rc basados en manifiesto
- Escriba una nueva función que incremente un nuevo contador
- Escribe una nueva función que tomará el valor del contador
Total: 4-5 archivos modificados en diff-e en aras de un solo contador y sufriendo constantemente el trabajo con el manifiesto XML, que es la fuente de información en el código plus. Esto es lo que nos ofrece Microsoft.
En realidad, la solución inventada parece aterradora, pero agregar un nuevo contador se realiza exactamente 1 línea en un archivo. Además, todo se genera automáticamente usando macros y, desafortunadamente, un script precompilado, ya que el manifiesto XML todavía es necesario, aunque ahora no es el principal.
Nuestro perfcounters_ctr.h se ve casi idéntico al ejemplo anterior:
#ifndef NV_PERFCOUNTER #error "You cannot do this!" #endif ... NV_PERFCOUNTER(copied_bytes) NV_PERFCOUNTER(copied_files) ... #undef NV_PERFCOUNTER
Como escribí anteriormente, la adición de contadores se realiza cargando el manifiesto XML utilizando lodctr.exe. Desde nuestro programa, solo podemos inicializarlos y modificarlos.
Los fragmentos de inicialización que nos interesan en el abridor generado se ven así:
#define COPIED_BYTES 0
Total: necesitamos una correspondencia de la forma "nombre de contador - índice creciente", y en la etapa de compilación, necesitamos saber el número de contadores y recopilar una matriz de inicialización de los índices de contador. Aquí es donde la X-macro viene al rescate.
Hacer coincidir un nombre de contador con su índice ascendente es bastante simple.
El siguiente código se convertirá en una clase enum, cuyos índices internos comienzan en 0 y se incrementan en uno. Agregando el último elemento con nuestras manos, inmediatamente descubrimos cuántos contadores totales tenemos:
enum class counter_enum : int { #define NV_PERFCOUNTER(ctr) ctr, #include "perfcounters_ctr.h" total_counters };
Y además, según nuestra enumeración, necesitamos inicializar los contadores:
static constexpr int counter_count = static_cast<int>(counter_enum::total_counters); const PERF_COUNTERSET_INFO counterset_info{ ... counter_count, ... }; struct { PERF_COUNTERSET_INFO set; PERF_COUNTER_INFO counters[counter_count]; } counterset { counterset_info, {
El resultado fue que la inicialización del nuevo contador ahora toma 1 línea y no requiere cambios adicionales en otros archivos (anteriormente, cada regeneración cambiaba 3 piezas de código solo en la inicialización).
Y agreguemos una API conveniente para incrementar los contadores. Algo en el espíritu:
#define NV_PERFCOUNTER(ctr) \ inline void ctr##_tick(size_t diff = 1) { } #include "perfcounters_ctr.h" #define NV_PERFCOUNTER(ctr) \ inline size_t ctr##_get() { } #include "perfcounter_ctr.h"
El preprocesador generará hermosos captadores / setters para nosotros, que podemos usar inmediatamente en el código, por ejemplo:
inline void copied_bytes_tick(size_t diff = 1); inline size_t copied_bytes_get();
Pero todavía tenemos 2 cosas tristes: el manifiesto XML y el archivo .rc (por desgracia, es necesario).
Lo hicimos lo suficientemente simple: un script previo a la compilación que lee el archivo original con macros que definen contadores, analiza lo que está entre "NV_COUNTER (" y ")", y en base a esto genera ambos archivos que están en .gitignore para que No tire basura.
Era : Software especial basado en el código de codificación generado por manifiesto XML. Muchos cambios en el proyecto para cada adición / eliminación del contador.
Ahora: el preprocesador y el script de pregeneración generan todos los contadores, el manifiesto XML y el archivo .rc. Exactamente una línea en diff-e para agregar / eliminar un contador. Gracias al preprocesador que ayudó a resolver este problema, mostrando en este caso particular más bien que mal.