Codelícia
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.
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.
Postado por Leonardo Moura às 12:25 0 comentários
Marcadores: cpp, cpp11, tutoriais
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.
T number_;
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.
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.
Postado por Leonardo Moura às 20:04 0 comentários
Postado por Leo Hero às 19:58 0 comentários
Funtores em C++, parte I
Ponteiros para funções são legais. Mas são muito C. Para deixar nosso código mais C++, podemos usar Funtores. Funtores são classes que possuem o operator (); dessa maneira podemos chamar a função usando o identificador do objeto como uma função. Vamos transformar a função do artigo anterior em um funtor.
double operator ()(double x){
return x * x - 1.0;
Para utilizar o funtor temos que criar uma instância dessa classe, e chamá-la através da referência ao objeto.
cout << f(2);
Para usar nossa função root, então, precisaríamos passar uma referência ao objeto:
double root(Function &f, double i, double e);
O problema aqui é que perdemos a generalidade da função root, já que ela só funciona para um objeto do tipo Function. Para torná-la mais genérica, vamos utilizar polimorfismo. Abaixo está o código completo da solução. Na linha 6 definimos uma classe abstrata para funções, depois criamos a classe MyFunction que vai herdar de Function, sobreescremos então o operator(). Na linha 18, temos a nova assinatura da função, agora ela recebe um ponteiro para um objeto do tipo Function. Assim, com o operador new podemos criar qualquer tipo de classe que derive de Function como parâmetro de nossa função root.
bool eqlZero(T x) {
return x * x < 0.000000001;
virtual double operator ()(double x) = 0;
class MyFunction : public Function{
// finds one root between i and e
double root(Function* f, double i, double e) {
int iterationsLeft = 10000;
while(iterationsLeft--) {
double x = (i+e) / 2.0;
if(eqlZero((*f)(x))){
}else if((*f)(x) < 0) {
Function* f = new MyFunction();
double result = root(f, -10.0, 10.0);
Teremos assim o mesmo resultado que a função do artigo anterior. Mas além de estilística, não existe uma razão aqui para usar funtores (muito pelo contrário, vamos ter uma penalidade de desempenho devido ao overhead). Na próxima parte vamos ver a vantagem de usar funtores.
Postado por Leo Hero às 18:08 0 comentários
Ponteiros para funções em c++
Ponteiros são legais, mais legal ainda são ponteiros para funções. Imagina que queremos criar uma função que encontra uma raiz de uma função em um intervalo, teremos, então, uma função que tem uma função como parâmetro.
Sua assinatura vai ser assim:
double root(double (*f)(double), double i, double e);
O "f" é o identificador do ponteiro, do lado esquerdo fica o valor de retorno da função, e entre parênteses, do lado direito, os parâmetros. Imagina que quiséssemos um ponteiro para uma função com dois parâmetros, teríamos então:
Então se quiséssemos a usar no corpo da função usaríamos, por exemplo:
f(1.0, 5.2);
Aqui está o meu código, que acha a raíz da função x² - 1. Se quisermos usar a função root para outra função, só precisamos trocar o ponteiro.
double root(double (*f)(double), double i, double e) {
if(eqlZero(f(x))){
} else if(f(x) < 0) {
Esse código dá como saída o seguinte (que é quando vale a variável x a cada iteração):
0.998535
Outra forma de implementar essa função root genérica, seria com funtores (classes com o operator ()), mas isso seria demais para esse caso. Saiba mais sobre ponteiros para funções.