Capítulo 07: Algoritmos da STL, Ranges e Expressões Lambda

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


🗺️ Mapa Conceitual de Algoritmos STL e Expressões Lambda Modernas

flowchart TD
    STL["Algoritmos da STL C++20"] --> Ranges["Namespace std::ranges"]
    STL --> Lambdas["Expressões Lambda"]

    subgraph RANGES_ADV["Vantagens de std::ranges"]
        NoIter["Fim do Pareamento Manual (.begin(), .end())"]
        Projections["Projeções Diretas (&Objeto::campo)"]
        ConceptsConstrained["Restrições Formais via Concepts"]
    end

    subgraph LAMBDA_ADV["Recursos Avançados de Lambdas"]
        Generic["Lambdas Genéricas ([](auto x))"]
        Captures["Cláusulas de Captura Explícitas ([this], [&], [=])"]
        ConstexprLambda["constexpr Lambdas (Compile-Time)"]
    end

    Ranges --> RANGES_ADV
    Lambdas --> LAMBDA_ADV

    RANGES_ADV --> Exec["Execução Otimizada de Alta Performance"]
    LAMBDA_ADV --> Exec

    style STL fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style RANGES_ADV fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style LAMBDA_ADV fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style Exec fill:#e8f5e9,stroke:#4caf50,stroke-width:2px

🏛️ 1. O Fim da Era vec.begin(), vec.end()

Desde o C++98, a biblioteca de algoritmos (<algorithm>) exigia que toda invocação recebesse pares explícitos de iteradores. Essa convenção gerava verbosidade desnecessária e abria margem para erros graves em tempo de execução quando programadores passavam iteradores de coleções distintas acidentalmente (std::sort(v1.begin(), v2.end())).

Com o C++20 Ranges, os algoritmos foram reescritos sob o namespace std::ranges. Eles operam diretamente sobre o contêiner inteiro como uma unidade lógica:

#include <vector>
#include <algorithm>

std::vector<int> dados = {9, 2, 7, 1, 5};

// C++98 / C++11 (Verboso)
std::sort(dados.begin(), dados.end());

// C++20 (Moderno, Seguro e Conciso)
std::ranges::sort(dados);

⚙️ 2. Projeções: Ordenação Declarativa Sem Boilerplate

Uma das adições mais poderosas do std::ranges são as projeções (projections). Uma projeção é um invocável (ou ponteiro para função-membro) que transforma temporariamente a visão de um elemento durante a execução do algoritmo, sem alterar o dado armazenado.

Considere ordenar uma lista de estruturas complexas pelo campo de prioridade:

#include <vector>
#include <string>
#include <algorithm>

struct Tarefa {
    int id;
    std::string nome;
    int prioridade;
};

std::vector<Tarefa> tarefas = {
    {1, "Backup", 10},
    {2, "Compilacao", 90},
    {3, "Limpeza", 5}
};

// Ordena as tarefas decrescentemente pela propriedade 'prioridade' diretamente!
std::ranges::sort(tarefas, std::greater<>{}, &Tarefa::prioridade);

📦 3. Expressões Lambda Modernas e Cláusulas de Captura

As expressões lambda permitem a criação de objetos-função anônimos (functors) em linha com zero overhead de chamada indireta (já que o compilador realiza inlining agressivo).

3.1 Anatomia da Expressão Lambda:

[captura](parâmetros) especificadores -> tipo_retorno { corpo }

3.2 Regras Críticas de Captura para Sistemas Concorrentes:

  • []: Captura vazia. A função é pura e pode ser convertida implicitamente em um ponteiro de função do C (void(*)()).
  • [&]: Captura tudo por referência. Rápido, mas extremamente arriscado em operações assíncronas (risco de dangling reference caso a variável local saia de escopo antes do fim da thread).
  • [=]: Captura tudo por valor (cópia). Seguro para transferências de escopo entre threads.
  • [x = std::move(ptr)]: Captura por movimento (init-capture), permitindo transferir objetos não copiáveis (como std::unique_ptr) para dentro da lambda.

📊 4. Comparativo de Algoritmos: STL Clássica vs C++20 Ranges

Operação Sintaxe Clássica (std::) Sintaxe C++20 (std::ranges::)
Ordenação std::sort(v.begin(), v.end()) std::ranges::sort(v)
Busca Linear std::find(v.begin(), v.end(), val) std::ranges::find(v, val)
Contagem Condicional std::count_if(v.begin(), v.end(), fn) std::ranges::count_if(v, fn)
Ordenação por Membro Lambda comparadora manual de 3 linhas Projeção direta &Struct::membro
Validação de Tipos Erros de template ilegíveis Rejeição amigável via Concepts

🔬 5. Laboratório de Arquitetura: Algoritmos Paralelos e Políticas de Execução (<execution>)

A biblioteca de algoritmos da STL oferece suporte nativo a paralelismo multi-core e vetorização SIMD de hardware através do cabeçalho <execution>.

5.1 As Quatro Políticas de Execução Padronizadas:

  1. std::execution::seq: Execução estritamente sequencial na thread chamadora.
  2. std::execution::par: Execução paralela dividida entre múltiplas threads do pool do runtime.
  3. std::execution::par_unseq: Paralela e vetorizada (SIMD). Operações podem ser intercaladas em registradores vetoriais AVX-512. Não deve conter travas de mutex ou alocações!
  4. std::execution::unseq (C++20): Vetorizada em uma única thread (SIMD pipelined).
#include <algorithm>
#include <execution>
#include <vector>

std::vector<double> dataset(10'000'000, 3.14);

// Ordenação paralela multi-core que satura todos os núcleos da CPU:
std::sort(std::execution::par, dataset.begin(), dataset.end());