Capítulo 03: Estruturas Condicionais, Init-Statements e Constexpr

Especialização em Sistemas com C++ • C++20 & C++23 • RAII, Smart Pointers, Templates, Concorrência e Alta Performance


🗺️ Mapa Conceitual de Controle de Fluxo e Avaliação em Tempo de Compilação

flowchart TD
    Cond["Decisão Lógica"] --> Runtime["Avaliação em Tempo de Execução"]
    Cond --> CompileTime["Avaliação em Tempo de Compilação"]

    subgraph RUNTIME_FLOW["Fluxo Dinâmico (Runtime)"]
        Init["Init-Statement: if (auto x = calc(); cond)"]
        SwitchInit["switch (init; valor)"]
        BranchPredict["Atributos de Hardware: [[likely]] / [[unlikely]]"]
    end

    subgraph COMPILE_FLOW["Fluxo Estático (Zero-Cost)"]
        IfConstexpr["if constexpr (std::is_integral_v<T>)"]
        ConstEval["consteval (Garantia de Execução no Compilador)"]
        Elimination["Poda Completa de Código Morto na Emissão do Binário"]
    end

    Runtime --> RUNTIME_FLOW
    CompileTime --> COMPILE_FLOW

    style Cond fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style RUNTIME_FLOW fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style COMPILE_FLOW fill:#e8f5e9,stroke:#4caf50,stroke-width:2px

🏛️ 1. Init-Statements: Escopo Mínimo e Prevenção de Vazamento de Estado

Um dos princípios fundamentais da escrita de código robusto em C++ é o Princípio do Menor Privilégio aplicado a escopos de variáveis. Antes do C++17, variáveis necessárias apenas para testar uma condição precisavam ser declaradas fora do bloco condicional, poluindo o escopo exterior e permitindo seu reuso acidental.

Com os Init-Statements, variáveis auxiliares existem exclusivamente dentro do escopo do if ou switch:

#include <mutex>
#include <optional>

std::mutex g_mutex;
std::optional<int> buscar_dado();

void rotina_segura() {
    // A trava std::lock_guard e o dado só existem dentro do if/else!
    if (std::lock_guard lock(g_mutex); auto res = buscar_dado()) {
        processar(*res);
    } else {
        tratar_ausencia();
    } // Aqui, 'lock' é destruído e o mutex liberado automaticamente!
}

⚙️ 2. if constexpr: Metaprogramação Elegante e Poda de Código

Antes de if constexpr (C++17/C++20), a especialização condicional de funções de acordo com propriedades de tipos exigia técnicas arcanas de SFINAE (Substitution Failure Is Not An Error) com std::enable_if_t ou sobrecarga massiva de classes de traits.

Com if constexpr:

  1. O compilador avalia a expressão booleana estaticamente.
  2. A ramificação não selecionada não é compilada para o tipo em questão.
  3. Não há saltos (jmp ou cmp) no código de máquina final.
#include <type_traits>
#include <iostream>

template <typename T>
void serializar(T valor) {
    if constexpr (std::is_pointer_v<T>) {
        if (valor) std::cout << *valor << '\n';
        else std::cout << "nullptr\n";
    } else if constexpr (std::is_integral_v<T>) {
        std::cout << "Inteiro: " << valor << '\n';
    } else {
        std::cout << "Tipo generico\n";
    }
}

📦 3. Otimização de Pipeline: Atributos [[likely]] e [[unlikely]]

Os processadores modernos operam com pipelines de instruções superescalares profundos e unidades de previsão de desvio (Branch Predictors). Quando uma ramificação é mal prevista, a CPU descarta dezenas de ciclos de instruções especulativas (branch misprediction penalty).

No C++20, foram introduzidos os atributos padronizados [[likely]] e [[unlikely]], permitindo aos engenheiros instruir o compilador a organizar o layout do código gerado (reordenando blocos básicos na seção .text) para que a ramificação mais comum execute em linha direta na cache de instruções L1i:

int despachar_pacote(int codigo_erro) {
    switch (codigo_erro) {
        case 0: [[likely]]
            return executar_caminho_rapido(); // Fluxo nominal otimizado
        case -1: [[unlikely]]
            return logar_falha_conexao();     // Desvio raro
        default: [[unlikely]]
            return abortar_processo();
    }
}

📊 4. Matriz de Decisões Condicionais em C++

Recurso Sintático Momento de Avaliação Efeito no Código de Máquina Caso de Uso Primário
if clássico Tempo de Execução Instruções de salto (je, jne) Decisões baseadas em dados de entrada do usuário ou rede
if (init; cond) Tempo de Execução Saltos com escopo estrito de pilha Limpeza e contenção de escopo de travas e retornos opcionais
if constexpr Tempo de Compilação Poda total do ramo inativo (Zero Bytes) Despacho de templates e especialização por traits
[[likely]] Compilação (Otimizador) Reordenação de blocos no executável Maximização do hit de Branch Predictor de CPU

🔬 5. Laboratório de Arquitetura: consteval e Funções Imediatas no C++20

Enquanto uma função marcada como constexpr pode ser executada tanto em tempo de compilação quanto em tempo de execução (caso seus argumentos dependam de entradas de runtime), o C++20 introduziu a palavra-chave consteval para declarar Funções Imediatas (Immediate Functions).

5.1 A Garantia Absoluta de Zero Custo de Runtime

Uma função consteval só pode ser executada pelo compilador. Se qualquer tentativa for feita de chamá-la com parâmetros desconhecidos em tempo de compilação, o build é sumariamente abortado:

consteval uint64_t hash_djb2(std::string_view str) {
    uint64_t hash = 5381;
    for (char c : str) {
        hash = ((hash << 5) + hash) + static_cast<uint64_t>(c);
    }
    return hash;
}

// O cálculo ocorre 100% no compilador; o executável recebe apenas a constante pré-calculada:
constexpr auto HASH_LOGIN = hash_djb2("API_ROUTER_AUTH_KEY");

Isso viabiliza a geração de tabelas de roteamento e decodificação de strings em switches numéricos sem consumir nenhum ciclo de clock da CPU em produção.