Capítulo 10: Ponteiros Inteligentes: unique_ptr, shared_ptr e weak_ptr
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
deletequando 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:
- O objeto
T. - O contador de referências fortes (Strong Reference Count).
- 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.