Por que Utilizar Smart Pointers?
Considere um cenário onde recursos alocados dinamicamente podem não ser liberados devido a exceções ou erros de programação. Por exemplo:
int Divide(int a, int b) {
if (b == 0) throw "Erro de divisão por zero";
return a / b;
}
void Processa(int x, int y) {
int* recurso1 = new int;
int* recurso2 = new int;
Divide(x, y);
delete recurso1;
delete recurso2;
}
int main() {
int val1, val2;
cin >> val1 >> val2;
try {
Processa(val1, val2);
} catch (const char* msg) {
cout << msg << endl;
} catch (...) {
cout << "Exceção desconhecida" << endl;
}
return 0;
}
Se uma exceção for lançada durante a alocação de recurso2 ou na chamada de Divide, os recursos anteriormente alocados não serão liberados, causando vazamentos de memória. Smart pointers automatizam a liberação, resolvendo esse problema.
Vazamentos de Memória
O que é um Vazamanto de Memória?
Um vazamento de memória ocorre quando um programa aloca memória mas fallha em liberá-la quando não é mais necessária. Isso pode levar ao consumo excessivo de recursos e degradação de desempenho, especialmente em sistemas de longa duração.
Classificação dos Vazamentos
Em C++, vazamentos podem ocorrer em memória heap (alocada via new ou malloc) ou em recursos do sistema (como descritores de arquivo). Ambos requerem estratégias de prevenção adequadas.
Prevenção de Vazamentos
Abordagens incluem o uso de RAII (Resource Acquisition Is Initialization), smart poinetrs, e ferramentas de detecção. O RAII garante que recursos sejam adquiridos durante a construção de objetos e liberados durante a destruição.
Fundamentos dos Smart Pointers
RAII em Prática
RAII é um idiom que vincula o ciclo de vida de recursos ao ciclo de vida de objetos. Smart pointers como unique_ptr e shared_ptr implementam RAII para gerenciar memória.
template <typename tipo="">
class PonteiroInteligente {
public:
PonteiroInteligente(Tipo* ptr) : _ptr(ptr) {}
~PonteiroInteligente() { delete _ptr; }
Tipo& operator*() { return *_ptr; }
Tipo* operator->() { return _ptr; }
private:
Tipo* _ptr;
};</typename>
Evolução dos Smart Pointers
auto_ptr (descontinuado) usava transferência de recursos, levando a problemas como ponteiros inválidos. unique_ptr proíbe cópias, permitindo apenas movimentação. shared_ptr utiliza contagem de referências para compartilhamento seguro.
template <typename tipo="">
class CompartilhadoPtr {
public:
CompartilhadoPtr(Tipo* ptr) : _ptr(ptr), _contador(new int(1)) {}
~CompartilhadoPtr() {
if (--(*_contador) == 0) {
delete _ptr;
delete _contador;
}
}
// Construtor de cópia e operador de atribuição atualizam o contador
private:
Tipo* _ptr;
int* _contador;
};</typename>
Resolvendo Ciclos de Referência
Ciclos de referência ocorrem quando objetos com shared_ptr se referem mutuamente, impedindo a liberação. weak_ptr quebra esses ciclos ao não incrementar a contagem de referências.
template <typename tipo="">
class FracoPtr {
public:
FracoPtr() : _ptr(nullptr) {}
FracoPtr(const CompartilhadoPtr<tipo>& sp) : _ptr(sp.obter()) {}
Tipo* obter() const { return _ptr; }
private:
Tipo* _ptr;
};</tipo></typename>
Deleters Personalizados
Para recursos não alocados com new, smart pointers podem usar deleters customizados. Por exemplo, para arrays ou objetos de arquivo:
std::shared_ptr<int> arrPtr(new int[10], [](int* p) {
delete[] p;
});</int>
Na implementação, isso pode ser integrado usando std::function ou templates para o deleter.
Smart Pointers no C++11 e Beyond
O C++11 padronizou unique_ptr, shared_ptr e weak_ptr, inspirados em bibliotecas como Boost. Essas ferramentas são essenciais para código moderno em C++, garantindo gerenciamento de memória robusto e seguro.