sexta-feira, 7 de setembro de 2012

Pointeiros Inteligentes (smart_pointers) parte 1: unique_ptr

C++ é amado (ou odiado) por muitos pelo controle que dá ao programador. A gerência de memória é feita quase que manualmente no código, ainda não temos gerência automática de memória. A sua implementação está prevista para a versão atual, mas ainda não há suporte na maioria dos compiladores.

Gerência automática de memória é excelente por um lado, mas como o usuário não tem controle sobre quando a limpeza da memória vai ser feita, podemos ter comportamentos estranhos no nosso programa. Em certas aplicações, como as de tempo real, queremos ter garantias sobre o tempo de execução, por isso a gerência manual da memória ainda é relevante.

Porém, como o programado é um ser humano (em geral), ele comete erros. Principalmente quando o projeto começa a ficar muito grande. Então temos as temidos vazamentos de memória. Esse fenômeno acontece quando criamos um objeto com o operador new, mas esquecemos de deletá-lo. Então aquele setor da memória fica ocupado e o programa não consegue mais utilizá-lo.

Outro problema acontece quando múltiplas partes do código compartilham o mesmo objeto contido na memória dinâmica. De quem é a responsabilidade de deletá-lo? um método pode deletar um objeto que ainda está sendo utilizado por outro método.

Para solucionar tudo isso, C++11 contém ponteiros inteligentes, que são estruturas que garantem que os dados da memória serão liberados quando não forem mais necessários.

Nesse artigo, vamos aprender sobre o primeiro tipo de ponteiro inteligente: unique_ptr. Esses tipos de ponteiros garantes que apenas um ponteiro vai conter a referência ao objeto de cada vez. Para que se possa passar o objeto adiante, deve-se "liberá-lo" do ponteiro.

Hora de ver unique_ptr em ação. Vamos primeiro definir um objeto que exibe uma mensagem quando está sendo deletado.
class Data{
public:
 void aMethod(){ cout << "method called" << endl;}
 ~Data(){ cout << "data is being deleted" << endl; }
};
Agora quando o ponteiro sai fora do escopo, o objeto associado é deletado.
{
  unique_ptr<Data> dataPtr { new Data() };
}
Para passar um objeto adiante, o método release deve ser usado. Assim temos a garantia que o objeto só tem um dono. A saída do programa abaixo seria o primeiro ponteiro sendo 0 (porque já liberou o objeto) e o segundo sendo igual ao ponteiro para o objeto.
unique_ptr<Data> dataPtr = unique_ptr<Data>(new Data());
unique_ptr<Data> dataPtr2 {dataPtr.release()};

cout << dataPtr.get() << " and " << dataPtr2.get() << endl;
Ponteiros inteligentes se comportam como ponteiros normais, então é possível chamar métodos, acessar elementos de um vetor, etc. Abaixo um exemplo da chamada de um método.
dataPtr2->aMethod();
Finalmente, uso mais interessante de ponteiros inteligentes é o retorno de objetos criados dentro de funções. O retorno de um ponteiro inteligente garante que os clientes da função tem que usar um ponteiro inteligente para receber os dados.
unique_ptr<int> createOne(){
 return unique_ptr<int>(new int(1));
}

// [...]

unique_ptr<int> one_p = createOne();
cout << *one_p << endl;
O código completo pode ser visto no github, em unique_ptr.cpp.

Nenhum comentário:

Postar um comentário