As tendências modernas de desenvolvimento de C ++ sugerem a máxima rejeição possível de macros no código. Mas às vezes sem macros, e em sua manifestação especialmente feia, não se pode fazer, pois sem elas é ainda pior. Sobre isso e a história.
Como você sabe, a primeira etapa na compilação de C e C ++ é o pré-processador, que substitui as diretivas de macros e pré-processador por texto sem formatação.
Isso nos permite fazer coisas estranhas, por exemplo, como:
Após o pré-processador funcionar, esse mal-entendido se transformará no código correto:
std::string str = "look, I'm a string!" ;
Obviamente, esse cabeçalho terrível não pode ser incluído em nenhum outro lugar. E sim, devido ao fato de adicionarmos esse cabeçalho várias vezes ao mesmo arquivo - sem #pragma uma vez ou incluir guardas.
Na verdade, vamos escrever um exemplo mais complexo que fará coisas diferentes com a ajuda de macros e, ao mesmo tempo, defenderá #include aleatório:
Isso ainda é feio, mas um certo encanto já aparece: quando você adiciona um novo elemento à classe enum, ele será automaticamente adicionado à instrução de saída sobrecarregada.
Aqui você pode formalizar a área de aplicação desse método: a necessidade de geração de código em locais diferentes, com base em uma fonte.
E agora a triste história do X-Macro e Windows. Existe um sistema como o Windows Performance Counters, que permite enviar determinados contadores ao sistema operacional para que outros aplicativos possam buscá-los. Por exemplo, o Zabbix pode ser configurado para coletar e monitorar qualquer contador de desempenho. Isso é bastante conveniente e você não precisa reinventar a roda com o retorno / consulta de dados.
Sinceramente, pensei que a adição de um novo contador parece a HANDLE counter = AddCounter ("name"). Ah, como eu estava errado.
Primeiro, você precisa escrever um manifesto XML especial (
exemplo ) ou gerá-lo usando o programa ecmangen.exe no Windows SDK, mas por
algum motivo esse ecmangen
foi removido das novas versões do Windows 10 SDK. Em seguida, você precisa gerar o código e o arquivo .rc usando o utilitário
ctrpp com base em nosso manifesto XML.
A adição de novos contadores ao próprio sistema é feita apenas com o utilitário
lodctr com nosso manifesto XML no argumento.
O que é um arquivo .rc?Esta é uma invenção da Microsoft, não relacionada ao C ++ padrão. Usando esses arquivos, você pode incorporar recursos em exe \ dll, como strings \ icons \ pictures etc., e depois buscá-los usando a API especial do Windows.
Os Perfcounters usam esses arquivos .rc para localizar nomes de contadores, e não está muito claro por que esses nomes devem ser localizados.
Resumindo o acima: para adicionar 1 contador, você precisa:
- Alterar manifesto XML
- Gere novos arquivos de projeto .c e .rc com base no manifesto
- Escreva uma nova função que incrementará um novo contador
- Escreva uma nova função que terá o valor do contador
Total: 4-5 arquivos modificados em diff-e, para obter um único contador e sofrer constantemente ao trabalhar com o manifesto XML, que é a fonte de informações no código positivo. É isso que a Microsoft nos oferece.
Na verdade, a solução inventada parece assustadora, mas a adição de um novo contador é feita exatamente 1 linha em um arquivo. Além disso, tudo é gerado automaticamente usando macros e, infelizmente, um script de pré-compilação, pois o manifesto XML ainda é necessário, embora agora não seja o principal.
Nosso perfcounters_ctr.h parece quase idêntico ao exemplo acima:
#ifndef NV_PERFCOUNTER #error "You cannot do this!" #endif ... NV_PERFCOUNTER(copied_bytes) NV_PERFCOUNTER(copied_files) ... #undef NV_PERFCOUNTER
Como escrevi anteriormente, a adição de contadores é feita carregando o manifesto XML usando lodctr.exe. No nosso programa, podemos apenas inicializá-los e modificá-los.
Os fragmentos de inicialização que nos interessam no abridor gerado são assim:
#define COPIED_BYTES 0
Total: precisamos de uma correspondência no formato “nome do contador - índice crescente” e, no estágio de compilação, é necessário conhecer o número de contadores e coletar uma matriz de inicialização dos índices do contador. É aqui que a macro X é resgatada.
A correspondência de um nome de contador ao seu índice crescente é bastante simples.
O código abaixo se transformará em uma classe enum, cujos índices internos iniciam em 0 e aumentam em um. Adicionando o último elemento com as mãos, descobrimos imediatamente quantos contadores totais temos:
enum class counter_enum : int { #define NV_PERFCOUNTER(ctr) ctr, #include "perfcounters_ctr.h" total_counters };
Além disso, com base em nossa enumeração, precisamos inicializar os 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, {
O resultado foi que a inicialização de um novo contador agora ocupa 1 linha e não requer alterações adicionais em outros arquivos (anteriormente, cada regeneração alterava três partes do código apenas na inicialização).
E vamos adicionar uma API conveniente para incrementar contadores. Algo no espírito:
#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"
O pré-processador irá gerar belos getters / setters para nós, que podemos usar imediatamente no código, por exemplo:
inline void copied_bytes_tick(size_t diff = 1); inline size_t copied_bytes_get();
Mas ainda temos duas coisas tristes: o manifesto XML e o arquivo .rc (infelizmente, é necessário).
Nós simplificamos bastante - um script de pré-compilação que lê o arquivo original com macros definindo contadores, analisa o que está entre "NV_COUNTER (" e ")" e, com base nisso, gera os dois arquivos que estão no .gitignore, para que não desarrume diffs.
Foi : Software especial baseado no código de codificação gerado pelo manifesto XML. Muitas mudanças no projeto para cada adição / remoção do contador.
Agora: o script de pré-processador e pré-construção gera todos os contadores, o manifesto XML e o arquivo .rc. Exatamente uma linha no diff-e para adicionar / remover um contador. Agradeço ao pré-processador que ajudou a resolver esse problema, mostrando neste caso em particular mais bem do que mal.