Capítulo 10: Ponteiros Inteligentes: unique_ptr, shared_ptr e weak_ptr

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


🗺️ Mapa Conceitual da Tríade de Smart Pointers da Biblioteca Padrão

flowchart TD
    SmartPtr["Ponteiros Inteligentes (<memory>)"] --> Unique["std::unique_ptr<T> (Posse Exclusiva)"]
    SmartPtr --> Shared["std::shared_ptr<T> (Posse Compartilhada)"]
    SmartPtr --> Weak["std::weak_ptr<T> (Referência Fraca Sem Posse)"]

    subgraph UNIQUE_PROPS["Características de unique_ptr"]
        ZeroOverhead["Zero Overhead: Idêntico a T* em Assembly"]
        MoveOnly["Move-Only: Não pode ser copiado, apenas movido"]
        CustomDeleter["Deleter Customizado Suportado"]
    end

    subgraph SHARED_PROPS["Características de shared_ptr"]
        ControlBlock["Control Block: Contador de Referência Atômico"]
        MakeShared["Alocação Única via std::make_shared"]
    end

    subgraph WEAK_PROPS["Características de weak_ptr"]
        BreaksCycles["Quebra Ciclos Circulares de Referência"]
        LockMethod["Promove para shared_ptr via .lock()"]
    end

    Unique --> UNIQUE_PROPS
    Shared --> SHARED_PROPS
    Weak --> WEAK_PROPS

    style SmartPtr fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style Unique fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style Shared fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style Weak fill:#ede7f6,stroke:#7e57c2,stroke-width:2px

🏛️ 1. O Fim de new e delete Explícitos no C++ Moderno

No C++ moderno, a chamada direta a delete em código de aplicação é considerada um code smell grave. As diretrizes oficiais do C++ Core Guidelines estabelecem que todo objeto no Heap deve ter sua vida governada por uma abstração de propriedade clara através de ponteiros inteligentes definidos em <memory>.


⚙️ 2. std::unique_ptr: Propriedade Exclusiva com Zero Overhead

O std::unique_ptr representa a posse estrita de um único dono. Não é permitido criar cópias de um std::unique_ptr, prevenindo qualquer ambiguidade sobre quem é o responsável pela destruição do objeto:

  • Tamanho Físico: Exatamente o mesmo tamanho de um ponteiro bruto (sizeof(unique_ptr<T>) == sizeof(T*)).
  • Performance: Nenhuma instrução adicional é emitida pelo compilador; o destrutor apenas invoca delete quando o ponteiro sai de escopo.
  • Criação Canônica: Sempre instancie via std::make_unique<T>(args...) (C++14/C++20).
#include <memory>

class HardwarePort { /* ... */ };

// Instanciação moderna segura contra vazamento de exceções
auto porta = std::make_unique<HardwarePort>();

// Transferência de Posse Exclusiva via Move Semantics
std::unique_ptr<HardwarePort> nova_posse = std::move(porta);
// 'porta' agora é nullptr; 'nova_posse' gerencia o recurso

📦 3. std::shared_ptr e std::weak_ptr: Posse Compartilhada

Quando múltiplos subsistemas independentes precisam manter um objeto vivo enquanto pelo menos um deles ainda precisar dele, utiliza-se std::shared_ptr.

3.1 O Bloco de Controle (Control Block)

Ao ser criado via std::make_shared<T>(), o C++ aloca em um único bloco de memória contíguo:

  1. O objeto T.
  2. O contador de referências fortes (Strong Reference Count).
  3. O contador de referências fracas (Weak Reference Count).

O incremento e decremento desses contadores utilizam operações atômicas da CPU (lock inc em x86), sendo thread-safe no gerenciamento de vida, embora o acesso aos dados internos do objeto compartilhado ainda exija sincronização por mutex se houver mutação concorrente.

3.2 O Perigo dos Ciclos e o Remédio std::weak_ptr

Se dois objetos mantiverem referências std::shared_ptr mútuas um para o outro (por exemplo, em nós de grafos ou observadores de eventos), seus contadores nunca chegarão a zero, resultando em um vazamento permanente de memória (Memory Leak). O std::weak_ptr resolve essa questão porque referencia o objeto sem incrementar o contador de referências fortes:

#include <memory>

struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev; // Referência fraca segura contra ciclo!
};

📊 4. Matriz Comparativa: Ponteiros Inteligentes da STL

Ponteiro Overhead de Espaço Overhead de Tempo Copiável? Caso de Uso Canônico
std::unique_ptr 0 bytes (8 bytes total) Zero Não (Apenas Move) 90% dos casos de alocação de objetos no Heap
std::shared_ptr 16 bytes adicionais (Control Block) Incrementos atômicos Sim Recursos compartilhados entre múltiplos donos assíncronos
std::weak_ptr 16 bytes (aponta para control block) Custo de .lock() Sim Quebra de ciclos em grafos e padrões Observer

🔬 5. Laboratório de Arquitetura: Deleters Customizados para APIs C e Recursos do SO

Uma das capacidades mais poderosas do std::unique_ptr é o suporte a Deleters Customizados, permitindo envelopar ponteiros do C legados (como FILE*, descritores de sockets ou handles de bibliotecas gráficas) dentro da semântica RAII estrita sem precisar criar classes invólucro pesadas:

#include <memory>
#include <cstdio>

// Deleter personalizado stateless (Zero custo de tamanho!)
struct FileCloser {
    void operator()(std::FILE* fp) const noexcept {
        if (fp) {
            std::fclose(fp);
            std::puts("Arquivo fechado automaticamente via RAII!");
        }
    }
};

using UniqueFile = std::unique_ptr<std::FILE, FileCloser>;

void processar_arquivo() {
    UniqueFile arq(std::fopen("telemetria.log", "r"));
    if (!arq) return;

    // Opera sobre o arquivo normalmente: arq.get()
    // Ao sair da função, FileCloser é invocado incondicionalmente!
}

Essa técnica elimina a necessidade de goto cleanup; comum em código procedural em C, trazendo segurança moderna para chamadas de sistema POSIX e Win32.