Para ver o artigo anterior da série: Smart Pointer – Introdução
Agora que já vimos o funcionamento básico de um smart pointer, vamos ver como usar um smart pointer que vem junto com a biblioteca padrão do C++.
Um ponteiro automático (auto_ptr) é semelhante a classe de smart pointer que mostrei no post anterior. Ele mantem o ponteiro para o dado em uma variável interna e quando seu destrutor é chamado ela se encarrega de destruir (fazer um delete) no ponteiro.
Mas a nossa classe de smart pointer tem um serio problema, se fizermos:
void funcao()
{
Pointer p = new int(0);
Pointer p2 = p;
}
//ou entao
Pointer alocaInt(int v)
{
return(Pointer(new int(v));
}
O código acima simplesmente vai dar problemas, porque o int alocado com new vai ser “deletado” duas vezes,a primeira por p e a segunda por p2! Ops! Para evitar isso, podemos solucionar o problema usando algumas técnicas como:
- Declarar o construtor de cópia e operador de atribuição como privados, bloqueando assim cópias de ponteiros (não resolve o problema, mas evita que ele ocorra).
- Gerenciar os ponteiros para que apenas uma instância do objeto Pointer apague ele (usando contagem de referencia por exemplo).
- Simplesmente quando um pointer for inicializado usando construtor de cópia ou operador de atribuição, ao invés de copiarmos os valores, vamos move-los (solução do auto_ptr).
Todas as soluções acima possuem seus prós e contras, não vou discutir aqui qual é melhor ou pior, porque cada uma acaba sendo melhor que a outra em determinados problemas. A solução do auto_pointer (que consiste em mudar o “dono” do ponteiro) é melhor (por este aspecto apenas) que a contagem de referencia por não precisar alocar um objeto extra para contar referencias ou criar um membro na classe para tal fim. Mas em contra-partida, não permite tanta flexibilidade quanto esta (não podemos usar o auto_ptr com conteiners da STL, por exemplo).
Se você simplesmente alocar um objeto na heap, fazer algumas operações e depois destruí-lo, o auto_ptr é uma das melhores soluções, pois uma boa implementação não traz overhead algum. Exemplo:
#include <memory>
#include <iostream>
int main(int argc, char **argv)
{
using namespace std;
auto_ptr<int> p(new int(3));
cout << p.get() << endl;
auto_ptr<int> p2 = p;
cout << p.get() << endl;
cout << *p2 << endl;
return(0);
}
No incrível exemplo acima, primeiro criamos um auto_ptr p e alocamos um inteiro. Após imprimir o valor do ponteiro, criamos um novo auto_ptr p2 e inicializamos ele com p. Nesse momento, p2 passa a apontar para o inteiro alocado anteriormente (agora ele é o dono) e p simplesmente aponta para NULL.
No final do código imprimimos o valor do ponteiro p e o valor apontado por p2 (o inteiro 3).
Outro exemplo:
#include <memory>
#include <iostream>
using namespace std;
class Objeto
{
public:
Objeto(int v): m_valor(v){}
~Objeto()
{
cout << "obj " << m_valor << " morreu" << endl;
}
int valor(){ return m_valor; }
private:
int m_valor;
};
auto_ptr<Objeto> alocaObj(int v)
{
return(auto_ptr<Objeto>(new Objeto(v)));
}
int main(int argc, char **argv)
{
using namespace std;
try
{
auto_ptr<Objeto> p = alocaObj(5);
cout << p->valor() << endl;
alocaObj(10);
throw "danou-se";
}
catch(...)
{
cout << "ops!!!" << endl;
}
return(0);
}
Esse exemplo é muito mais bacana que o anterior! Tem até exceções! O legal desse exemplo é que podemos ver que não é preciso se preocupar em destruir qualquer um dos objetos alocados, porque o auto_ptr se encarrega disso. Note também que caso alguem lance um exceção, o auto_ptr não nos deixa na mão e se encarrega de limpar a sujeira.
Outro detalhe também é que na segunda chamada a alocaObj nós nem mesmo nos preocupamos em pegar o valor de retorno, e o auto_ptr mesmo assim deu conta do recado!
Conclusão
Para casos simples onde precisamos apenas controlar a vida de um objeto no escopo de uma função ou entre chamadas de funções, ao invés de usarmos delete manualmente (sujeito a erros, pois em algum fluxo podemos esquecer de usa-lo) o auto_ptr vem a calhar como uma ferramenta simples e eficiente. No próximo post vamos começar a ver as classes de smart_pointer da boost, que podemos utilizar inclusive com a STL!
Próximo artigo da série: Boost Shared Pointer
maio 8, 2009 às 7:51 |
[...] Auto Pointer (auto_ptr) [...]