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.

domingo, 2 de setembro de 2012

Asserções Estáticas (em tempo de compilação) em C++11

Nos próximos posts vamos explorar algumas novidades do novo padrão do C++: o C++11. Vamos começar por asserções estáticas.

Uma asserção é teste que garante a corretude do código. C++ já possuía asserções, contidas na <cassert>, que poderiam ser ativadas só no modo de debug. A vantagem das asserções estáticas é que elas são executadas em tempo de compilação, não acarretando nem prejuízo em performance. Elas podem ser usadas, por exemplo, para garantir certas propriedades estáticas de classes usadas por clientes.

Como exemplo, vamos implementar uma classe que contenha um número. Queremos garantir que somente tipo numéricos vão poder ser usados como parâmetro.
class Function{
template <typename T>
class Number {
  T number_;
public:
  Number(T number) : number_(number) {
    // flags a compilation error if experession is false
    static_assert(std::is_arithmetic<T>::value,
                            "type must be a number"); 
  }
};
Na linha 8 temos o static_assert, que recebe dois parâmetros: a expressão, que deve ser verdadeira, e a mensagem de erro a ser exibida. Caso a expressão seja falsa, o código não vai compilar. Agora se usarmos um tipo int ou float, não teremos nenhum problema de compilação.
class B {

};

int main()
{
  double d;
  int i;
  B b;
  
  Number<double> cd(d); // OK, d is a floating-point number 
  Number<int> ci(i);    // OK, i is Integral
  Number<B> cb(b);    // Not OK, b is a class 
}
Mas se utilizarmos um tipo não-primitivo, teremos o seguinte erro:
static_assert.cpp: In constructor ‘Number<T>::Number(T) [with T = B]’:
static_assert.cpp:27:17:   instantiated from here
static_assert.cpp:11:5: error: static assertion failed: "type must be a number"
O código completo pode ser visto no github, em static_assert.cpp.