C++: Ponteiros Inteligentes Avançados
Última atualização: 2026-08-26
Na aula 45, aprendemos sobre semântica de movimentação.
Agora, vamos nos aprofundar nos smart pointers — a ferramenta central do gerenciamento de memória moderno do C++.
Com smart pointers, você nunca mais precisará se preocupar com vazamentos de memória.
1. Visão Geral dos Smart Pointers
(1) 1.1 O Que São Smart Pointers?
Smart pointers são classes template que gerenciam memória dinâmica, liberando a memória automaticamente (RAII).
Três tipos de smart pointers:
| Smart Pointer | Função | Caso de Uso |
|---|---|---|
unique_ptr |
Propriedade exclusiva | Propriedade exclusiva de um objeto |
shared_ptr |
Propriedade compartilhada | Múltiplos locais precisam acessar o mesmo objeto |
weak_ptr |
Referência fraca | Quebrar referências circulares |
2. unique_ptr
(1) 2.1 Uso Básico
unique_ptr é um smart pointer com propriedade exclusiva — não pode ser copiado, apenas movido.
Exemplo: uso básico de unique_ptr (Dificuldade ⭐)
▶ Exemplo 2: Exemplo de código (Dificuldade ⭐)
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> p1(new int(42));
std::cout << *p1 << std::endl; // Saída: 42
// std::unique_ptr<int> p2 = p1; // ❌ Erro! Não pode copiar
std::unique_ptr<int> p2 = std::move(p1); // ✅ Pode mover
std::cout << *p2 << std::endl; // Saída: 42
return 0;
} // p2 libera a memória automaticamente
Saída:
(Saída do programa)
(2) 2.2 Deleter Personalizado
Exemplo: Usando unique_ptr para gerenciar um arquivo (Dificuldade ⭐⭐)
#include <iostream>
#include <memory>
#include <cstdio>
// Deleter personalizado: fechar arquivo
struct FileDeleter {
void operator()(FILE* fp) const {
if (fp) {
fclose(fp);
std::cout << "File closed" << std::endl;
}
}
};
int main() {
std::unique_ptr<FILE, FileDeleter> file(fopen("test.txt", "w"));
// Não precisa de fclose manual, unique_ptr chama FileDeleter automaticamente
return 0;
}
3. shared_ptr
(1) 3.1 Uso Básico
shared_ptr é um smart pointer com propriedade compartilhada, gerenciando memória com contagem de referências.
Exemplo: uso básico de shared_ptr (Dificuldade ⭐)
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> p1 = std::make_shared<int>(42);
std::cout << "Reference count: " << p1.use_count() << std::endl; // 1
{
std::shared_ptr<int> p2 = p1; // Cópia, contagem de referências +1
std::cout << "Reference count: " << p1.use_count() << std::endl; // 2
} // p2 destruído, contagem de referências -1
std::cout << "Reference count: " << p1.use_count() << std::endl; // 1
return 0;
}
(2) 3.2 make_shared vs new
Recomendação: Use std::make_shared em vez de new.
| Comparação | new |
make_shared |
|---|---|---|
| Segurança de exceção | Pode vazar | Seguro |
| Desempenho | Duas alocações | Uma alocação |
| Clareza do código | Verboso | Conciso |
// Recomendado
auto p1 = std::make_shared<int>(42);
// Não recomendado
std::shared_ptr<int> p2(new int(42));
4. weak_ptr
(1) 4.1 Por Que Precisamos do weak_ptr?
Problema: shared_ptr pode causar referências circulares, levando a vazamentos de memória.
Exemplo: Referência circular (Dificuldade ⭐⭐⭐)
#include <iostream>
#include <memory>
struct Node {
std::shared_ptr<Node> next; // Referência circular!
~Node() { std::cout << "Node destroyed" << std::endl; }
};
int main() {
auto n1 = std::make_shared<Node>();
auto n2 = std::make_shared<Node>();
n1->next = n2; // Referência circular
n2->next = n1;
return 0;
} // ❌ n1 e n2 não serão liberados (vazamento de memória)
(2) 4.2 Quebrando Referências Circulares com weak_ptr
Solução: Substitua um dos shared_ptr por weak_ptr.
struct Node {
std::weak_ptr<Node> next; // Usar weak_ptr, não incrementa a contagem de referências
~Node() { std::cout << "Node destroyed" << std::endl; }
};
int main() {
auto n1 = std::make_shared<Node>();
auto n2 = std::make_shared<Node>();
n1->next = n2; // Não incrementa a contagem de referências
n2->next = n1;
return 0;
} // ✅ Liberado corretamente
5. Guia de Seleção de Smart Pointers
(1) 5.1 Como Escolher?
| Cenário | Recomendação |
|---|---|
| Propriedade exclusiva | unique_ptr |
| Propriedade compartilhada | shared_ptr |
| Observador | weak_ptr ou ponteiro bruto |
| Arrays | unique_ptr<T> |
(2) 5.2 O Que Não Fazer
| Erro | Explicação |
|---|---|
| Não inicialize múltiplos shared_ptrs a partir do mesmo ponteiro bruto | Causará double free |
| Não misture ponteiros brutos e smart pointers | Quebra o RAII |
| Não chame delete manualmente | Deixe os smart pointers gerenciarem |
6. Prática: Gerenciando Recursos com Smart Pointers
▶ Exemplo 1: Usando smart pointers para gerenciar uma conexão de banco de dados (Dificuldade ⭐⭐⭐)
#include <iostream>
#include <memory>
// Conexão de banco de dados simulada
class DatabaseConnection {
public:
DatabaseConnection() {
std::cout << "Connecting to database" << std::endl;
}
~DatabaseConnection() {
std::cout << "Disconnected" << std::endl;
}
void query(const std::string& sql) {
std::cout << "Executing SQL: " << sql << std::endl;
}
};
int main() {
// Usar unique_ptr para gerenciar recurso exclusivo
std::unique_ptrDatabaseConnection conn(new DatabaseConnection());
conn->query("SELECT * FROM users");
return 0;
} // Desconecta automaticamente
Saída:
Connecting to database
Disconnected
Executing SQL:
❓ Perguntas Frequentes
P: Qual é mais rápido, unique_ptr ou shared_ptr? R:
unique_ptré mais rápido (sem overhead de contagem de referências). Prefiraunique_ptr.
P: Quando devo usar ponteiros brutos? R: - Quando você não é dono do recurso (observador) - Ao interagir com bibliotecas C - Quando o desempenho é extremamente crítico (mas geralmente desnecessário)
P: O shared_ptr é thread-safe? R: - Modificações na contagem de referências são thread-safe - Mas o objeto apontado não é thread-safe (precisa de proteção com mutex)
▶ Exemplo 3: Propriedade compartilhada com shared_ptr (Dificuldade ⭐)
#include <iostream>
#include <memory>
class Resource {
public:
Resource() { std::cout << "Construct" << std::endl; }
~Resource() { std::cout << "Destruct" << std::endl; }
};
int main() {
{
std::shared_ptr<Resource> p1 = std::make_shared<Resource>();
std::shared_ptr<Resource> p2 = p1; // Propriedade compartilhada
std::cout << "Reference count: " << p1.use_count() << std::endl;
}
// Sai do escopo, destrói automaticamente
std::cout << "Program ended" << std::endl;
return 0;
}
Saída:
Construct
Destruct
Reference count:
Program ended
shared_ptr compartilha a propriedade e libera automaticamente quando a contagem de referências chega a zero. Usar make_shared é mais seguro para criação.
| Ponto-Chave | Resumo |
|---|---|
| unique_ptr | Propriedade exclusiva, não pode ser copiado |
| shared_ptr | Propriedade compartilhada, contagem de referências |
| weak_ptr | Referência fraca, quebra referências circulares |
| make_shared | Abordagem recomendada, seguro contra exceções |
| Princípio de seleção | Prefira unique_ptr, use shared_ptr quando necessário |
📖 Resumo
- shared_ptr: Propriedade compartilhada, gerenciado por contagem de referências
- unique_ptr: Propriedade exclusiva, não copiável
- weak_ptr: Referência fraca, não incrementa a contagem de referências
- make_shared/make_unique: Métodos de criação recomendados
📝 Exercícios
-
Básico (Dificuldade ⭐): Use
unique_ptrpara gerenciar umintalocado dinamicamente, criado commake_unique. Tente copiar ounique_ptr(deve falhar na compilação), então usemove. -
Intermediário (Dificuldade ⭐⭐): Use
shared_ptrpara implementar múltiplos objetos compartilhando o mesmo recurso. Crie doisshared_ptrs apontando para o mesmo objeto e exibause_count()para observar as mudanças na contagem de referências. -
Desafio (Dificuldade ⭐⭐⭐): Use
weak_ptrpara resolver um problema de referência circular comshared_ptr. Crie classes A e B que mantenhamshared_ptrs um para o outro e observe o vazamento de memória. Depois troque paraweak_ptre verifique a liberação correta.
- unique_ptr tem propriedade exclusiva, não copiável mas movível
- shared_ptr tem propriedade compartilhada, gerenciado por contagem de referências
- weak_ptr combinado com shared_ptr evita referências circulares
- make_unique/make_shared são formas seguras contra exceções para criação
- Deleters personalizados lidam com liberação especial de recursos
Próxima aula: Introdução à Metaprogramação de Templates (#47)