Capítulo 05: Coleções Contíguas: std::array, std::vector e std::span

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


🗺️ Mapa Conceitual de Coleções Contíguas e Visões de Memória

flowchart TD
    Memoria["Memória Contígua"] --> Stack["Stack (Tamanho Fixo em Compile-Time)"]
    Memoria --> Heap["Heap Dinâmico (Tamanho Expansível em Runtime)"]

    Stack --> StdArray["std::array<T, N> (Sem Overhead de Alocação)"]
    Heap --> StdVector["std::vector<T> (Capacidade, Tamanho e Realocação)"]

    subgraph VIEW_LAYER["Camada de Abstração Zero-Copy (C++20)"]
        StdSpan["std::span<T> (Ponteiro + Tamanho - Non-Owning View)"]
    end

    StdArray --> VIEW_LAYER
    StdVector --> VIEW_LAYER
    RawArray["C-Style Array T[]"] --> VIEW_LAYER

    VIEW_LAYER --> Algorithms["Funções e Algoritmos Unificados Sem Cópia"]

    style Memoria fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style VIEW_LAYER fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style Algorithms fill:#e8f5e9,stroke:#4caf50,stroke-width:2px

🏛️ 1. O Triunfo da Localidade Espacial de Referência

Em computação de alto desempenho, a eficiência de um algoritmo é dominada pela hierarquia de memória da máquina física (Registradores -> L1 Cache -> L2 Cache -> L3 Cache -> RAM principal). Acessar dados contíguos na memória permite que o hardware execute pre-fetching automático de linhas de cache de 64 bytes.

Por essa razão, contêineres contíguos (std::array e std::vector) superam estruturas baseadas em nós (como std::list ou árvores balanceadas) por ordens de magnitude em tarefas do mundo real, mesmo quando a complexidade assintótica teórica sugeriria o contrário.


⚙️ 2. std::array vs std::vector: Stack vs Heap

2.1 std::array<T, N> (Stack Memory)

  • Tamanho N conhecido estaticamente em tempo de compilação.
  • Não aloca nenhum byte no Heap do sistema operacional.
  • Custo de inicialização e destruição instantâneo.
  • Suporta todas as operações de segurança da STL, como .at(index) para verificação de limites (bounds checking).

2.2 std::vector<T> (Heap Memory)

  • O contêiner de propósito geral por excelência no C++.
  • Gerencia um buffer contíguo alocado dinamicamente através de três ponteiros internos: begin, end e end_of_storage.
  • A Regra de Ouro: Sempre utilize .reserve(capacidade) quando souber a estimativa de elementos prévia. Isso elimina realocações repetidas de memória (malloc/free) e cópias caras de objetos.
  • emplace_back: Constrói o objeto diretamente no buffer do vetor, eliminando a criação de objetos temporários intermediários.

📦 3. std::span (C++20): A Visão Uniforme de Memória

Antes do C++20, se você quisesse escrever uma função de processamento de áudio ou telemetria que operasse sobre buffers de floats, você era forçado a escolher entre:

  1. Receber const std::vector<float>& (impossibilitando o uso com std::array ou vetores legados do C).
  2. Criar sobrecargas para cada contêiner.
  3. Passar o par inseguro const float* ptr, size_t count.

O std::span<T> resolve esse problema definitivamente:

  • É uma referência não proprietária (non-owning view) contendo apenas o ponteiro base e a quantidade de elementos.
  • Não realiza nenhuma alocação dinâmica.
  • Suporta sub-intervalos através de .subspan(offset, count).
  • É compatível transparentemente com std::vector, std::array, std::string ou arrays estáticos brutos.
#include <span>
#include <iostream>

void processar_telemetria(std::span<const int> dados) {
    std::cout << "Processando " << dados.size() << " elementos na memoria.\n";
    for (int v : dados) {
        // Acesso contíguo otimizado
    }
}

📊 4. Matriz Comparativa: Contêineres Contíguos

Característica std::array<T, N> std::vector<T> std::span<T> (C++20)
Local de Armazenamento Stack (ou embutido na struct) Heap (via alocador dinâmico) Nenhum (apenas aponta para memória externa)
Tamanho Mutável? Não (fixo em compile-time) Sim (cresce dinamicamente) Não (reflete o buffer apontado)
Overhead de Espaço 0 bytes além dos dados 24 bytes (3 ponteiros de 64 bits) 16 bytes (ponteiro + tamanho)
Propriedade (Ownership) Possui os dados Possui os dados Não possui (visão temporária)

🔬 5. Laboratório de Arquitetura: Arenas de Memória e PMR (Polymorphic Memory Resources)

Em sistemas de tempo real rígido (Hard Real-Time), invocar o alocador padrão do sistema operacional (malloc) dentro de um std::vector é proibido devido ao risco de contenção de travas do kernel e latência indeterminística.

5.1 O Namespace std::pmr (C++17/C++20)

O C++ moderno padronizou alocadores polimórficos no namespace std::pmr. Com ele, o tipo do vetor não muda com o tipo do alocador:

#include <vector>
#include <memory_resource>
#include <array>

// Buffer estático pré-alocado na Stack (Zero chamadas ao Kernel!)
std::array<std::byte, 1024 * 64> stack_pool;
std::pmr::monotonic_buffer_resource pool(stack_pool.data(), stack_pool.size());

// Vetor que utiliza a memória estática da Stack como sua Heap privada:
std::pmr::vector<int> dados(&pool);
dados.reserve(1000); // Alocação ultra-rápida via bump pointer em nanossegundos!

O uso de monotonic_buffer_resource reduz o tempo de alocação para poucas instruções de CPU (bump allocation), liberando todo o bloco de uma só vez na destruição do recurso de memória.