Capítulo 04: Estruturas Repetitivas, Range-Based For e C++20 Ranges

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


🗺️ Mapa Conceitual da Iteração e Pipelines Funcionais com C++20 Ranges

flowchart LR
    Container["Coleção Contígua (std::vector / std::array)"] --> Pipe["Operador Pipe |"]
    
    subgraph RANGES_VIEWS["Biblioteca de Views (<ranges>) - Avaliação Preguiçosa"]
        Filter["std::views::filter (Filtragem Sob Demanda)"]
        Transform["std::views::transform (Mapeamento Sem Alocação)"]
        Take["std::views::take (Truncamento do Fluxo)"]
    end

    Pipe --> Filter --> Transform --> Take
    Take --> Consumer["Consumidor Final (Range-based for / std::ranges::copy)"]

    style Container fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style RANGES_VIEWS fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style Consumer fill:#e8f5e9,stroke:#4caf50,stroke-width:2px

🏛️ 1. A Evolução dos Laços em C++: Segurança e Performance

A forma como iteramos sobre coleções de dados reflete o amadurecimento da linguagem ao longo das décadas:

  1. Laço Indexado Clássico do C (for (size_t i = 0; ...)): Suscetível a erros de off-by-one, estouro de limites de índice (buffer overflow) e dependente de indexação manual vec[i].
  2. Iteradores Explícitos da STL Clássica (for (auto it = vec.begin(); ...)): Mais genérico, porém verboso e propenso a invalidação de iteradores durante mutações.
  3. Range-Based For Loop (C++11/C++20): Sintaxe limpa e sem índice, com controle total sobre passagem por valor (auto x), por referência mutável (auto& x) ou por referência imutável (const auto& x).
#include <vector>
#include <string>

std::vector<std::string> logs = {"ERRO_01", "WARN_02", "INFO_03"};

// Padrão Ouro: Referência Constante para Evitar Cópias Pesadas no Heap
for (const auto& log : logs) {
    processar(log);
}

// C++20: Range-based for com Init-Statement
for (auto itens = obter_lista(); const auto& item : itens) {
    tratar(item);
}

⚙️ 2. A Biblioteca C++20 Ranges (<ranges>)

A adição dos Ranges no C++20 revolucionou o processamento algorítmico ao introduzir o conceito de Views (visões): adaptadores de intervalo que operam de maneira preguiçosa (lazy evaluation), sem alocar novos contêineres e sem copiar os elementos da coleção original.

2.1 Principais Adaptadores de Visão (std::views):

  • std::views::filter: Filtra elementos que satisfazem um predicado booleano.
  • std::views::transform: Aplica uma função de transformação em cada elemento.
  • std::views::take(N): Interrompe o fluxo após os primeiros N elementos.
  • std::views::drop(N): Descarta os primeiros N elementos.
  • std::views::reverse: Itera em sentido inverso sem alterar o contêiner base.
  • std::views::iota(start, end): Gera sequências aritméticas numéricas sem armazená-las fisicamente na RAM.
#include <ranges>
#include <vector>
#include <iostream>

std::vector<int> telemetria = {10, 25, 40, 55, 60, 75, 80};

// Composição funcional elegante via operador de pipe '|'
auto pipeline = telemetria
    | std::views::filter([](int n) { return n % 2 == 0; }) // Apenas pares
    | std::views::transform([](int n) { return n * 2; })   // Multiplica por 2
    | std::views::take(3);                                 // Pega os 3 primeiros

for (int v : pipeline) {
    std::cout << v << " "; // Saída: 20 80 120
}

📦 3. Benefícios de Performance: Zero Alocações Intermediárias

Em linguagens como Java ou JavaScript, pipelines como .filter().map() frequentemente instanciam novos arrays ou listas intermediárias no Heap, gerando fragmentação e pressão desnecessária no Garbage Collector.

Em C++20, uma view é simplesmente um par de ponteiros/iteradores leves (apenas 16 a 24 bytes na Stack). O cálculo de cada elemento só acontece no momento exato em que o laço consumidor solicita o próximo item (operator++). Quando o compilador otimiza o código (-O2), ele frequentemente funde (inlines) todo o pipeline em um único laço de máquina vetorizado com instruções SIMD.


📊 4. Matriz Comparativa: Métodos de Iteração em C++

Paradigma de Laço Overhead de Memória Legibilidade Segurança Contra Overflow
for (size_t i = 0) Zero Baixa (muito código boilerplate) Baixa (risco de índice inválido)
Iteradores begin/end Zero Média Média (risco de dereferenciar end())
Range-Based for Zero (usando const&) Alta e idiomática Total (gerenciado pelo compilador)
std::views (C++20) Zero (estruturas non-owning) Máxima (composição funcional pura) Total e preguiçoso (lazy)

🔬 5. Laboratório de Arquitetura: Janelas Deslizantes e Inovações do C++23 Ranges

Com a evolução para o padrão C++23, a biblioteca de Ranges foi expandida com adaptadores altamente solicitados pela comunidade de processamento de sinais e dados científicos:

5.1 Fatiamento em Lotes com std::views::chunk e slide

  • std::views::chunk(N): Agrupa elementos contíguos em blocos de tamanho N fixo, perfeito para processamento em lotes (batch processing) e envio de pacotes pela rede.
  • std::views::slide(N): Produz uma janela deslizante contínua de tamanho N, essencial para o cálculo de médias móveis (moving averages) e filtros passa-baixa em telemetria sem duplicar buffers.
  • std::views::zip: Combina duas ou mais coleções independentes em tuplas sincronizadas de referências, eliminando laços com múltiplos índices.
#include <vector>
#include <iostream>

// Simulação de pipeline com composição de adaptadores
std::vector<double> sinais = {1.0, 2.5, 3.8, 4.2, 5.0, 6.1};

// Exemplo conceitual de processamento de janela em C++23:
// auto janelas = sinais | std::views::slide(3);
// for (auto janela : janelas) { /* calcula média móvel da janela de 3 floats */ }

Essa expressividade aproxima o C++ da clareza declarativa de linguagens funcionais como Haskell, mantendo a performance inigualável de compilação em código de máquina vetorial.