Smart Pointers (ou ponteiros inteligente) são objetos em C++ cuja a função básica é monitorar ponteiros e desalocar os mesmos (dando um delete) quando estes não são mais utilizados. Mas como um Smart Pointer sabe a hora de destruir um objeto? Isso depende da implementação, algumas das técnicas utilizadas são:
- Contagem de referencias: Para cada alocação de memória, o smart pointer aloca um outro objeto que ele usa para contar quantos smart pointers apontam para a mesma região de memória, esse objeto é compartilhado entre todos os smart pointers que utilizam a mesma região de memória. Sempre que um novo smart pointer passa a apontar para uma região de memória, ele incrementa esse contador, quando ele é destruído, decrementa o contador. O ultimo smart pointer destruído (aquele que decrementar um contador e este chegar a zero) se encarrega de desalocar a memória.
- Controle de escopo: Essa implementação é a mais simples, ela se baseia no fato que todo objeto em C++ no final da sua vida, tem seu destrutor executado. Os ponteiros que fazem controle por escopo, simplesmente no seu destrutor destroem a região de memória para qual apontam. Esse tipo de ponteiro é apenas utilizados para controlar objetos que não costumam ser compartilhados, como por exemplo alocar um buffer dentro de uma função ou uma classe que possui como membro um objeto que precisa ser alocado dinamicamente.
Funcionamento Básico
Um smart pointer tira proveito do fato que todo objeto em C++ criado na pilha vai ter seu destrutor executado, tirando proveito dessa funcionalidade podemos criar o código a seguir:
class IntPointer
{
public:
IntPointer(int *p):pValue(p) {assert(p);}
~IntPointer(){ delete pValue; }
int *GetValue() { return pValue; }
private:
int *pValue;
};
A classe acima simplesmente é inicializada com um ponteiro para inteiros e no seu destrutor se encarrega de liberar a memória, podendo ser utilizada como no exemplo abaixo:
int main(int, char **)
{
IntPointer ptr(new int(5));
using namespace std;
cout << "Valor: " << *ptr.GetValue() << endl;
return(0);
}
Criando uma Classe Genérica
Note que no código anterior não nos preocupamos em desalocar a memória alocada no inicio da função. Mas existe um problema: E se quisermos armazenar floats ou qualquer outro tipo de dado? Podemos modificar o código com o uso de templates para que fique genérico:
template <typename T>
class Pointer
{
public:
Pointer(T *p): pValue(p) {assert(pValue);}
~Pointer() { delete pValue; }
T *GetValue() {return pValue; }
private:
T *pValue;
};
Agora podemos usar o código acima com qualquer tipo de dado primitivo, voltando ao exemplo anterior:
int main(int , char **)
{
Pointer<int> ptr(new int(5));
Pointer<float> ptrf(new float(5.3f));
using namespace std;<span> </span>
cout << "Valor: " << *ptr.GetValue() << endl;
cout << "Valor float: " << *ptrf.GetValue() << endl;
return(0);
}
A classe Pointer ainda não é tão inteligente assim, mas podemos também usar tipos de dado mais complexos:
struct Person
{
std::string name;
int age;
};
int main(int , char **)
{
Pointer<Person> ptr(new Person());
ptr.GetValue()->name = "SmartPointer";
using namespace std;
cout << ptr.GetValue()->name << endl;
return(0);
};
Sobrecarga de Operadores
Um item inconveniente da classe Pointer é o fato de que é necessário chamar o método “GetValue” sempre que é preciso acessar o ponteiro que ela armazena. Para abstrair isso, podemos usar sobrecarga de operadores e modificar o ponteiro para que ele fique um pouco mais esperto:
template <typename T>
class Pointer
{
public:
Pointer(T *p): pValue(p) {assert(pValue);}
~Pointer() { delete pValue; }
T *operator->()
{
return(pValue);
}
private:
T *pValue;
};
//Agora podemos escrever:
int main(int , char **)
{
Pointer<Person> ptr(new Person());
ptr->name = "SmartPointer";
using namespace std;
cout << ptr->name << endl;
return(0);
};
Conclusão
Com essa pequena classe temos uma implementação bem simples de um smart pointer (ela tem alguns problemas, como não tratar const corretamente, não funciona se o objeto pointer for copiado, arrays, etc), sendo assim, esta classe não serve para muita coisa a não ser como exemplo simples (digamos que não podemos chamar ela de smart).
Um detalhe interessante é que usando corretamente inlines e compilando o código acima em um bom compilador (com otimizações ligadas) usar essa classe de Pointer não traz sobrecarga nenhuma ao programa, não teremos nenhum custo adicional de performance ou consumo de memória usando esse tipo de construção. No Visual Studio, por exemplo, a classe inteira desaparece com otimizações ligadas, ficando apenas a chamada de delete que desaloca a memória.
Por hoje é apenas isso, mas no próximo post, vamos ver uma classe de smart pointer que faz parte da biblioteca padrão do C++: auto_ptr.
Próximo artigo da série: Auto Pointer (auto_ptr)
Junho 3, 2008 às 11:15 |
Ei, parabéns. Gostei do artigo, bem didático. Precisa sair a parte 2 explorando sua classe de smartptr.
Junho 3, 2008 às 11:27 |
Valeu Ze! Eu na verdade estou planejando falar das classes existentes (auto_ptr, os smart pointers da boost, etc). Fiz esse post apenas para dar uma noção para quem nao conhece do funcionamento de um smart pointer. Bom, acho que vou extender ela um pouco mais para incluir contagem de referencias
Junho 3, 2008 às 20:32 |
Massa! tá bem didático mesmo! parabéns! Aguardo a continuação =D
Junho 4, 2008 às 8:52 |
Obrigado Daniel, ta no forno, em breve to atualizando
.
Maio 8, 2009 às 7:51 |
[...] Smart Pointer – Introdução [...]