Tag Archives: cpp

C++: Parametrizar programa pela linha de comando, arquivo de configurações e/ou variáveis de ambiente

O problema

Olá amiguinhos! Recentemente, tive curiosidade de como fazer uma coisa um pouco mais complexa do que eu já vinha usando.

Todo mundo usa parâmetros para configurar o seu programa e não precisar recompilar ele toda vez para uma alteração nos dados e não na logica. Além de poder tonar o programa mais customizável, temos a vantagem de que desenvolver programas assim também é mais fácil e rápido(por motivos óbvios, use e confirme).

Até ai, eu estava fazendo e vivendo muito bem com isso usando a espetacular biblioteca de opções de programa, isso mesmo, a boost.program_options! Com ela, de forma fácil eu consigo fazer essa trabalho de parametrização do programa.

Só que, para quem já usou programas bem feitos, sabe que essa parametrização é pode ser feita de mais jeitos, e tradicionalmente, as 4 formas mais usadas: Por parâmetros de linha de comando, arquivo de configurações, variáveis de ambiente e/ou combinações dos 3 primeiros.

Eu queria fazer melhor, usar a forma mais completa, que é justamente a 4 forma. Eu ainda não sabia direito como fazer, mas sabia que a boost.program_options dava suporte a todas essas formas e talvez a mais algumas… Recentemente sentei, agrupei tudo o que eu já sabia, pesquisei algumas coisas, fiz uns testes e finalmente consegui chegar a um resultado razoável, que é o que eu lhes apresento agora ;).

A solução

Considere um programa chamado “sort“.

O programa precisa receber um arquivo de entrada e um de saída, sendo que valores padrões(a entrada padrão e a saída padrão) podem ser assumidos para esses dois caso eles não sejam fornecidos, e eles só devem ser configurados pela linha de comando.

O programa deve receber um numero de pivô, esse numero deve assumir um valor padrão caso não se diga o contrario por algum parâmetro.

O programa também deve exibir uma mensagem de ajuda, mas somente se for requisitado via linha de comando. Essa mensagem de ajuda serve para saber como entrar com todos os parâmetros, via variáveis de ambiente, linha de comando e/ou arquivo de configuração.

Algumas opções devem ser alteradas por variáveis de ambiente começando com “SORT_“. Essas variáveis são as que não são as dadas pela linha de comando exclusivamente.

Finalmente, o código que resolve todos esses requisitos é:

Além do que eu falei, o código tem algumas coisinhas a mais, mas o principal é que cumpre os requisitos.

Fácil de entender, fácil de fazer. Na verdade, eu tive mais dificuldades e demorei mais em escrever os requisitos do que em escrever o código :p.

Advertisements

Versão mais nova do gcc para windows

Boas novas

Quem usa a versão oficial para windows do gcc, sabe que a versão oficial suportada pelo windows do gcc e um pouco antiga…. Mais precisamente a versão 3.4. Tá, admito, é muito antiga…. E essa versão não suporta as melhores qualidades do gcc mais novo.
Bom, para aqueles que gostam de novidades e muito poucos riscos, estou avisando aqui que agora vocês pode ter o melhor compilador do mundo em seu computador com o sistema Windows.

Onde baixar

Através desse site http://www.tdragon.net/recentgcc/ você pode baixar a versão mais nova do gcc para o windows!

Que fique claro: Eu só testei via emulação(wine), portanto não vi os detalhes do compilador, só do instalador e de como desinstalar. Mais, segundo o que promete o site que você pode baixar o compilador, ele tem suporte a todas as funcionalidades legais do gcc mais novo. Entre elas:

  • Suporte a arquitetura core2.
  • Suporte a openmp.
  • Suporte a pthreads.

Só esses 3 itens já são suficientes para convencer a qualquer pessoa a pelo menos testar.

Recomendo também a leitura do site, lá ele pode explicar qualquer duvida que venha a existir. Inclusive sobre alguns bugs corrigidos e informações sobre o compilador.

Minhas Impressões

Apesar de não ter usado o compilador, eu baixei e instalei ele só para ter uma noção de como era o instalador  e sua praticidade.

Bom, posso dizer que fiquei impressionado ;). O instalador é melhor do que o do MinGW (o instalador que instala o gcc-3.4 no windows) e ainda é compatível com esse(O instalador é compatível).

Através do instalador você pode remover, alterar e instalar a versão mais nova do gcc no seu computador. Ainda existe a possibilidade de continuar um instalação passada, aproveitando os downloads dos pacotes do site que você já baixou.

Vale a pena mencionar que na instalação ele baixa os pacotes e instala na sua maquina. Ou seja, você tem que estar com acesso a intenet ou fingir uma continuação de instalação se você já tiver os pacotes baixados. O que falta eu acho que é uma versão alternativa completa, com os pacotes já no instalador para não precisar baixar do site os arquivos. Mas talvez já tenha, porque eu não procurei muito, então pode ser que exista e eu não sei ;).

Os detalhes

Vamos aos detalhes do projeto. Esse projeto tem duas versões do compilador, claro que na maioria da vezes você só ira usar uma, mais é sempre bom ter a outra opção. Essas opções são classificadas de acordo de como o binário ira resolver as excessões. Vou mostrar as duas opções abaixo, mais na maior parte das vezes a opção padrão e a melhor e você não deve se preocupar com essa parte.

Calma. Uma pausa antes de prosseguirmos. Se prepare, a partir de agora o nível vai descer. A leitura a partir de agora eu admito que é um pouco mais pesada, mas não se assuste!

Então vamos falar agora sobre como resolver pilhas! Let’s Go Talk About Stacks ;).

SJLJ

A primeira, que é a versão padrão do projeto, é a versão compilada SJLJ.

Desse trecho em diante é especulação, porque não sei direito o porque de usar SJLJ, mas posso especular de forma muito próxima da realidade.

A vantagem de usar a versão padrão com SJLJ é que em vez de usar outro metodo de resolução de exceções, o desenvolvedor desta versão do gcc usa as instruções setjmp(man setjmp) e a longjmp(man longjmp).

Sacou o porque do SJLJ? ;).

Usar essas funções em um binário é mais rápido para o binário a não ser que você use muitas excessões. Se você usar muitas excessões você ira ter um overhead nos try{…}. Isso é a excessão em códigos de computação científica e em geral, por isso, use a versão padrão SJLJ.

Detalhe, não há os try{..} que eu mencionei acima. Para quem programa em c++ é mais fácil dizer assim para que você possa entender, mais imagine que esses blocos são códigos em C que estão ai no começo e no final de uma porção de código para simular os blocos try{…}.

Ah sim, também é mais portável desenvolver algo em SJLJ, porque as funções setjmp e longjmp são funções da biblioteca padrão. E desconfio que seja mais fácil escrever código com SJLJ, apesar de nunca ter escrito com essas funções, eu consigo imaginar como implementar com elas as excessões, consigo ate imagina uma troca de contexto de hardware. Deve ser resultado da minha “forma de pensar em pilhas”.

DWARF-2

A segunda versão do compilador é a a versão não padrão compilada com DWARF-2(dw-2).

Também não sei muito sobre dw-2. Para falar a verdade, sei mais de SJLJ e quase nada de dw-2, e logo vou explicar o porque. Então como acima, o trecho a seguir é mais especulação ainda ;).

Vamos a vantagens do dw-2. Primeiro, não a overhead para as excessões. Ponto. Como ele faz isso? Ai entra a parte estranha do processo e o porque eu não sei quase nada do processo.

Primeiro devemos lembrar de uma vantagem do SJLJ: “portabilidade”. Porque isso é importante? Porque no dw-2 não tem nenhuma. A cada plataforma existe uma forma do compilador fazer o binário resolver a excessão de forma diferente sem overhead para cada maquina. Isso é muito complexo de se implementar pelo que eu posso ver. Envolve sinal em algumas plataformas, pilhas e frames em outras e qualquer outro método em outras plataformas. Isso é o que quer dizer não portável. Pode ser de qualquer forma que resolva o problema em outra plataforma ;).

Além disso, o código fica muito complexo e maior. Há formas de diminuir essa complexidade, mas ela ainda existe.

Fim

Bom, é isso. Baixem o gcc-4,3 para windows! Se eu usasse windows não perderia tempo ;).

Acho que eu acabei me alongando mais na parte de resolução de pilha no binário gerado, mas acho que foi empolgação. Fazia tempos que eu queria escrever algo assim, então acho que falar só do gcc para windows que eu nem uso seria um pouco down. E sempre desejei dar uma esclarecida sobre esse assunto. Bom, fica ai para a geração futura ;).

PS1:. Meu corretor ortográfico esta com defeito no site. Perdão erros ortográficos. ;).

PS2:. Esse post pode ter opiniões pessoais e tendenciosas. Se encontrar alguma, ignore e veja a mensagem geral do texto.


Mais uma dica de blog

Andando por ai, vi e passo a recomendar o blog do Murilo.

O cara é alguem com muito futuro e faz uns posts muito legais.

Antigamente rolou um post no blog do vidageek(nem vou colocar o endereço aqui, ele não merece…) sobre c/c++, basicamente esculhambando a linguagem. No blog do Murilo ele fez um post bem legal defendendo a linguagem. Vale a pena dar uma lida, para você poder se esclarecer melhor caso pense que c/c++ é uma linguagem ruim.

Também tem posts negando mitos, dando destaque a esse aqui.

Recomendo a quem programa em c/c++ que de uma lida nesse blog, porque ate eu quero que o meu blog se torne assim quando eu crescer :p.


Usando C++ com MPI: vector

MPI com vector<>

Bom, todos sabem que eu gosto de c++, e falar o porque seria assunto de outro tópico bem maior. Que eu não estou com saco de fazer ainda :p.

Mas, fazendo programas com MPI, eu acabei me deparando com uma coisa incrível: Ninguém fazia os programas em c++, só c….

Acho que um dos motivos, seria porque o povo não quer mesmo :p. mas, como todos nós sabemos, c++ não é mais lento que C em nenhum aspecto, então, porque não neh?

Nesse topico eu ia explicar como usar o vector com MPI, mas como já tem alguém que faz isso, eu vou só passar o link:

http://www.thinkingparallel.com/2007/02/08/a-smart-way-to-send-a-stdvector-with-mpi-and-why-it-fails/

Veja que na verdade ele não aconselha usar o vector em MPI, mas ensina como usar.

Como os itens do vector são guardados em posições contíguas, então os argumentos dele não valem para desmerecer o vector.


Tratamento de exceções: Parte I

Formas Tradicionais de Tratamento de Erro, sem Exceções

Para tratar e propagar erros, normalmente temos as seguintes opções:

  1. Terminar o Programa
    O método mais brusco e espartano. Não é bom porque alguns erros você quer tratar. Você faz isso normalmente com abort(); exit();
  2. Devolver um código de erro.
    Nem sempre é viável porque você pode não poder devolver um código de erro, seja porque esta devolvendo um valor que não pode ser invalido ou porque você não tem controle sobre o retorno.
  3. Devolver um valor valido e deixar o objeto em um estado invalido.
    Usado para testar depois os erros. Pode dar um certo trabalho manter essa estrategia thread-safe. Em “C”, você tem como exemplo a variável “errno”.
  4. Chamar uma função de erro especifica.
    Você cadastra uma função para cada tipo de erro que você vai encontrar.

 Sem tratamento de exceções, todas as formas de código acima sofrem do seguinte mal:

  • Propagação de erro manual através das chamadas de função(através da pilha de chamadas).
  • Erros não identificados continuam o programa, deixando em um estado invalido e difícil de depurar.
  • O código fica com a legibilidade prejudicada pelo código de tratamento de erro.

Tratando erro com exceções

  • Tratamento de erros separado da logica do programa.
    Você ganha um código mais legível.
  • Erros não identificados terminaram o programa.
    O programa não fica em um estado invalido que se propaga ate um ponto critico, e também ajuda a descobrir a origem do erro de forma clara.
  • E tratado apenas por quem sabe tratar o erro.
    Não há o risco de um erro não identificado ou não planejado cair em um tratamento destinado a outro tipo de erro.
  • Facilita o tratamento de erros.
    Separa o tratamento de um tipo de erro em apenas um local do código. Deixa mais fácil a manutenção do código de tratamento e a legibilidade.
  • Deixa mais simples a propagação de erros entre as camadas mais acima do código.

Mas, O que é uma exceção em C++?

É uma estrutura de controlo não local, baseada em pilha. Equivale a um retorno alternativo, feito com objetivo de propagar erros que ocorrem no programa que são “exceções” ao seu funcionamento normal.

É usada principalmente para tratamento de erros e suporte a tolerância a falhas.

C++” tem suporte a exceções e tem inclusive uma biblioteca padrão para exceções. Dentro dessa biblioteca, temos a classe “exception“, que é definida assim:

class exception{
	public:
		exception() throw();
		exception(const exception&) throw();
		exception& operator=(const exception&) throw();
		virtual ~exception() throw();
		virtual const char *what() const throw();
};

Capturando ou lançando exceções

Para capturar ou lançar exceções, você pode usar algo assim:

try{ // Começa bloco que vai gerenciar exceções.
	// Assim você dispara exceções. Pode ser qualquer objeto, mas é bom
	// que seja uma que tenha parentesco com "exception" ou que seja "exception".
	throw exception("ERROR!!"); // Criamos neste exemplo um do tipo exception
} //Termina bloco que vai gerenciar exceções. Começa o tratamento de cada exceção
catch( H ){ // Trata exceções do tipo "H" ou filhas, podia ser outro tipo.
	// Aqui dentro tratamos o erros para tipos "H".
}
catch( exception e ){ // Trata exceções do tipo exception ou filhas.
	// Aqui dentro tratamos o erros para tipos "exception".
}

O código que você escreveu para  “catch( H )” só sera executado se:

  1. Se “H” for igual a “exception”.
    Por exemplo: typedef exception H;
  2. Se “H” for base de publica não ambígua.
    Problema do diamante(problema de herança).
  3. exception e “H” forem ponteiros que satisfazem [1] e [2].
  4. Se “H” for referencia do tipo que satisfaz [1] e [2] em relação ao tipo de exception.

Recebendo informações de uma exceção

Sempre que você receber uma exceção que seja ou derive da classe “exception“, você pode usar o método “what();” para saber informações sobre a exceção, que sera retornado na forma de um “const char*“:

catch( std::exception &e ){
	std::cerr << e.what() << std::endl ; // Imprime na saida de erro informações sobre erro
}

 Caso você tenha lançado um objeto seu criado para o “throw“, você pode pegar qualquer atributo dele. Esse caso é para as classes derivadas de “exception“, que é o padrão.

Aviso:

Sempre passe para o catch por referencia:

catch( MyType &e ){

Se não pode haver problemas de herança.

Capturando exceções genéricas

Caso seja necessário capturar uma exceção que você não saiba de que tipo é(como por exemplo para fazer alguma finalização antes de propagar o erro), você pode fazer:

catch( ... ){
	std::cerr << "WTF?" << std::endl ;
	exit(1);
}

Redisparando exceções

Você pode precisar redisparar a exceção que você capturou para uma camada mais a cima do programa quando você não conseguir tratar por completo o erro, ou quando quiser que uma camada mais a cima do programa trate o erro ou encerre o programa.

Você pode fazer isso assim:

try{  .........   }
catch( ... ){
	delete x;
	throw; // Relança exceção não tratável aqui....
}

O que acontece quando as exceções não são tratadas por um “catch”?

O programa simplesmente acaba.

A exceção vai voltando nas chamadas de função, sempre em ordem contraria as funções chamadas até encontrar um “catch” que a segure. Caso isso não ocorra, ela chega no main e quando tenta voltar no main, acaba terminado o programa.

Assim você tem certeza que seu programa não vai estar em um estado invalido com um erro não tratado, ou terminar em um ponto muito depois do erro que seja muito difícil de depurar o problema.


Versão do gcc 4.5 lançada!

O gcc 4.5 foi lançado ontem depois de um período de desenvolvimento bem conturbado:p.

Com a versão nova do gcc, vem o suporte descontinuado e o aviso que nas próximas versões muitas arquiteturas iram deixar de ser suportadas e quais a partir da proxima versão também seram descontinuadas. Com a saida de algumas, outras entram, porque agora o gcc esta com suporte melhor as arquioteturas atom e arm.

Algo esperado também é o suporte parcial ao tão esperado c++0x. O gcc 4.5 tem suporte a maioria dos “features” do c++0x, com um suporte devendo apenas para a parte de concorrencia, mas esse suporte pode ser suprido por bibliotecas externas(como a boost) enquanto o padrão não é lançado de vez e o gcc não o suporta por completo.

Para quem quiser saber mais, pode ver na pagina de suporte ao c++0x do gcc 4.5, no propia pagina do gcc 4.5 ou no blog do phoronix.


smart pointer: auto_ptr

Bom, voltando a falar de C++…. Desculpa a demora, mas infelizmente o meu querido amigo V.N. não tinha devolvido meu caderno de rascunhos que eu fiz alguns rascunhos interessantes para postar aqui.

Mas, deixando as desculpas para lá, vamos ao que interessa. . .

Delete? free? Nunca mais!!!

Bom, primeiramente, a pergunta que não quer calar: “O que é um auto_ptr?”. Para quem se lembra de posts passados, simplesmente, um auto_ptr é um smart_pointer, ou seja, um objeto que implementa o padrão de projeto proxy.

Mas precisamente, sua função é a de resolver os “delete” de c++, deixando a função de desalocar um recurso para essa classe, quando o escopo em que o auto_ptr for terminado. Ou seja, o auto_ptr deleta para você a memoria que você alocou no final do escopo em que ele foi declarado(por isso que ele pode ser considerado um smart_pointer).

O auto_ptr não é nada mais além de uma template de uma classe, que tem como atributo interno um ponteiro para um tipo(já que ela é uma template, esse tipo pode variar…) e também sobrecarrega os operadores de ponteiros( “*” , “->” , …) para que você possa lidar com o objeto de forma que ele pareça um ponteiro(por isso que ele é um proxy).

Vale a pena dizer que o auto_ptr é da biblioteca padrão de C++, e que esta no namespace std do cabeçalho <memory>.

Contrato/API

Bom, essa classe nada mais faz do que deletar a memoria alocada no final, então, consequentemente, ele não “deveria” não interferir no acesso a memoria que esta alocado ;).

Inicializando ponteiros(ou: Adquirindo recursos)

Então vamos a como você faria para inicializar um ponteiro para um tipo “MyType” sem o auto_ptr:

{	/* começa aqui um escopo imaginário . . . */
	MyType *my_type=NULL ;
	my_type=new MyType ;

Então, com auto_ptr nos teríamos:

{	/* começa aqui um escopo imaginário . . . */
	auto_ptr<MyType> my_type( new MyType );

Sacou a diferença? Menos código e código mais natural? Ainda não sacou? Então, vamos pensar: No primeiro código, você não ganha o presente, que é desalocar o recurso no final do escopo ;). E veja que o segundo, o código e mais limpo e natural, e se escreve em uma linha ;).

Destruindo Ponteiros(ou: Liberando Recursos)

Certo, beleza, mas é ai? Como eu desaloco um recurso no final da minha função ou no final do meu “escopo”? No primeiro exemplo, vamos ver como “desalocar”/”liberar” o primeiro exemplo que fizemos:

	delete my_type ;
}	/* Fim do escopo imaginário. . . */

Agora vamos ver com o auto_ptr:

	/* Há!! Pegadinha do malandro!!! Aqui você não faz nada . . . */
}	/* Fim do escopo imaginário. . . */

Acessando Ponteiros/Recursos

Isso, vamos ver o como acessar o recurso com o auto_ptr. Agora, primeiramente, vamos ver como acessar o ponteiro do primeiro exemplo, o sem auto_ptr:

	int x = my_type->x ;
	my_type->x = 12345 ;

Tudo bem, mas como acesso isso com auto_ptr? Através de métodos? Gets and Sets? Magia negra? Não, da mesma forma. Isso mesmo, da mesma forma.

Não fiquem esperando que eu recorte e cole o código acima, porque é o mesmo :p. Isso se deve porque o “contrato” do auto_ptr não fica na parte de acesso ao recurso, e sim na criação e destruição do recurso. Então, teoricamente, o auto_ptr não deve interferir nesse sentido.

Outros detalhes da API

Certo, mas como eu poderia liberar um recurso antes do escopa acabar tranquilamente? Usar um delete? Não, é uma variável, não se pode dar um delete nela ;). Então se faz necessário que tenha um método que desaloque o recurso antes de terminar o escopo. Para isso existe o método “reset()“, que você usa simplesmente assim:

	my_type.reset();

Caso você passe um argumento sendo uma endereço de memoria, esse novo endereço vai ser o endereço que o auto_ptr vai fazer proxy dele. Estilo:

	my_type.reset( new MyType );

Veja que foi usado o “.” no lugar “->“. Isso porque você esta acessando o método da classe auto_ptr, e não da classe que ele esta fazendo “proxy“.

Mas uma coisa para se ressaltar na API, seria o construtor de copia do auto_ptr, porque, quando você faz uma atribuição de um auto_ptr para outro, a que é atribuído não é atribuído nada, ele só passa a fazer proxy do recurso do outro auto_ptr e o outro, passa a fazer proxy de nada(ou NULL, em outras palavras…). Isso é importante de lembrar porque o auto_ptr passa a ser invalido, então não tente usar ele depois de atribuir para outro auto_ptr.

Mais uma coisa, existe também um método chamado “.get()” que retorna um objeto que é a copia do objeto que o auto_ptr esta fazendo proxy. Então eu poderia fazer assim:

	MyType aux = my_type.get();

E também existe um método “.release()“, que libera o endereço que o auto_ptr esta fazendo proxy para um ponteiro normal. Legal para quando você precisa saber do endereço de onde esta, e/ou para passar para fora de funções caso você não retorne via auto_ptr. Então seria assim:

	MyType *aux = my_type.get();

Sabendo de tudo isso, vamos a um exemplinho básico, só pra ver a utilidade disso.

On The Road!!!!

Agora vamos ao exemplos. Fiz um programa para testar o tempo de criação, destruição e acesso de recursos. Você podem ver o código aqui.

Vale a pena dizer que eu usei a minha classe de medir tempo, para mais detalhes, veja aqui.

Falado isso, só falta mostrar a saída dos resultado:

Tempo de criação e destruição:
Ponteiro normal: 60s
Ponteiro automático: 60s
auto_ptr:
Teste de escrita: 53s
ptr normal:
Teste de escrita: 53s
auto_ptr:
Teste de acesso: 53s
ptr normal:
Teste de acesso: 53s

Bom, podemos ver que os tempos foram iguais ;). Porque? Simples, a proposta do auto_ptr é justamente não interferir no acesso e criação, e sim só destruir o objeto quando o escopo termina ;).

Bom, da para ver que não existe motivos para não usar auto_ptr(a não ser por alguns recursos técnicos, como ele não se comportar bem com conteniers da biblioteca padrão . . .), e também usar não gera overhead nenhum. Então, porque não usar neh?

Para considerações finais, só tenho a dizer que usei o gcc-4.3 e o pc que eu rodei esse exemplo era bem rápido. Portanto, se forem repetir o exemplo, coloquem -O2 ou equivalente no gcc e diminua o numero de operações que são feitas.

Dicas para Usar um auto_ptr(Ou: O que não fazer com auto_ptr):


Como Usar ponteiros “for Dummies”

Aprendendo do jeito fácil

Depois de uma grande viagem na internet, descobri uma coisa legal para aquelas pessoas que estão começando agora e errando com ponteiros em “c/c++”.

É vídeo feito pela universidade de stanford, mostrando o que você deve lembrar sobre ponteiros, o que são ponteiros e como usá-los.

Claro, esse vídeo não é nada muito complexo. É apenas para ser uma introdução simples, mas ainda vale muito a pena para quem esta começando(Ou para se divertir também 😉 ).


Aquisição e desalocação de recursos com objetos (Um pouco de RAII)

O que é a RAII?

Falar de RAII é ser sempre um pouco obvio, mas os conceitos que vem com ela de vez em quando não estão “fixos”. Sabendo o que é a técnica, o que ela faz e como implementar, podemos usa-la de forma melhor.

A RAII é um padrão de projeto que especifica que todos os recursos de um objeto devem ser adquiridos/alocados quando o objeto for inicializado.

E também, todos os recursos que um objeto tem que são exclusivos dele, devem ser liberados/desalocados no destrutor do objeto.

Fazendo isso, você tem um objeto que respeita o RAII.

Por que usar o RAII?

Bom, existem vários motivos para se usar o RAII. O mais convincente deles talvez seja que o C++(assim como a linguagem D), foi projetada para esse designe. Isso porque o C++ assume que o que você cria no objeto só vai existir no escopo daquele objeto.

Isso seria como o escopo de funções, você aloca/adquiri recursos no começo da sua função, como no exemplo abaixo:

/* exemplo de como seria o escopo */
void foo( int &x ){

	vector<double> tempArray;
	int *aux = new int ;

/* Aqui continua a sua função */
. . . .

No começo da função “foo”, você adquiriu o recurso “tempArray” e alocou um inteiro “aux“.

Isso seria justamente o que você iria fazer no construtor do objeto, e que deve ser o inicio do escopo de um objeto, comparado ao escopo de uma função.

A primeira coisa que é executado no objeto, pouco depois de adquirir recursos declarados será o construtor do objeto. Então, seria uma classe assim que seria a equivalente da função “foo”:

class foo {
/**Aqui você "adquiri" um recurso quando o objeto começa a existir.
 * Esse recurso deve ser deslocado automaticamente e executado seu destrutor
 * quando o objeto deixar de existir, porque saiu do "escopo".
 */
	vector<double> tempArray;
	int *aux;
/* Agora o construtor. Aqui você "aloca" os seus recursos dinâmicos.
 * Aqui é preferível que você também defina atributos do objeto.
 */
	foo( int &x ) {
		this->aux = new int ;
/* Aqui continua a sua classe */
. . . . .

Esse é o inicio do seu objeto, mais o RAII ainda não esta completo. O RAII também deve abranger o destrutor, porque é ele que vai “desalocar/liberar” os recursos que o seu objeto.

Quando é requisitado que o objeto deixe de existir, o destrutor vai ser executado e depois será destruído os atributos do objeto, Isso porque o destrutor, comparado com uma função, representa o final do código que está na função, antes da função acabar e sair de escopo. Por exemplo, esse é o final da função “foo” que foi mostrada no começo:

	/*Final da função foo, final do escopo da função.
	* Será desalocado todos os recursos dinâmicos alocados nessa
	* Função.
	*/
	delete aux ;
	/*Ainda temos o objeto tempArray que pertence a esse escopo. Mas,
	* como já é o final do escopo, esse recurso terá chamado seu destrutor
	* e será desalocado automaticamente.
	*/
}

Esse código seria o final da função foo, que esta nos ajudando a entender o como funciona o escopo de um objeto.

Esse código equivale ao que a esse destrutor do objeto:

~foo(){
	/*O destrutor de foo foi chamado. Isso significa que o objeto vai
	* deixar de existir, então será desalocado todos os recursos dinâmicos
	* que pertenção exclusivamente a esse objetos. No caso desse objeto,
	* this->aux contem um recurso que alocamos no construtor, então ele
	* precisa ser deletado:
	*/
	delete this->aux ;
	/*Ainda temos o objeto tempArray que pertence ao escopo do nosso objeto.
	* Mas, como já é o final do escopo, esse recurso terá chamado seu destrutor
	* e será desalocado automaticamente.
	*/
}

Bom, pelo que da para ver com esse construtor e esse destrutor, o objeto terá seus recursos adquiridos no construtor e desalocados no destrutor.

Vejam que isso se adequá de forma perfeita em “C++“. Parece até redundância ficar falando isso, mas não é assim em algumas linguagens e algumas pessoas não usam esse conceito direito.

Código Exception Safe

Também é possível escrever código “exception safe“, ou seja, código que seja seguro para o uso de excessões.

Isso significa que você poderá usar excessões em qualquer parte do código, de forma que o seu programa gerado não tenha nenhum problema de vazamento de memória ou consistência por causa de uma excessão gerada em alguma parte de seu código.

Você garante isso porque você defini muito bem o comportamento de um objeto. Se você consegue definir as ações do destrutor e do construtor bem, você garante que fora do escopo tudo daquele objeto vai ser destruído quando ele for finalizado. Então não importa se você chegou no final da sua rotina, o objeto vai ser eliminado assim que aquele escopo deixar de existir. Isso garante o “exception safe“.

(Caso você não saiba muito sobre exceções e “exception safe“, clique em “exception safe” no link no título desse tópico e veja o artigo Bjarne, o criador.)

Por que Não usar o RAII?

Não existe motivos para não usar RAII, existem motivos para não usar RAII completamente.

Então, porque não usar RAII completamente? Simplesmente porque, em alguns casos, os recursos que o objeto adquiri para si são muito grandes, e talvez seja mais vantajoso iniciar o objeto com uma parte do recurso ou até mesmo adiar a aquisição do recurso.

Isso ocorre por exemplo quando o objeto precisa de um vetor muito grande ou quando você não sabe se o recurso será realmente necessário ou não. Então, como uma estratégia para otimizar os recursos que seu objeto vai consumir, você opta por uma inicialização tardia ou sob-demanda.

Bons exemplos sobre isso seriam a classe da biblioteca padrão vector e list. As duas são exemplos dos dois casos.

Mas, independente da construção do objeto, a destruição ainda tem que ter o mesmo modo de agir. O único problema é que deve se checar no destrutor se o recurso já foi alocado e depois libera-lo ou não. Caso você faça uso de algum tipo de “smart pointer” (um dia falo mais sobre eles . . .) você não ira precisar nem verificar se o recurso foi alocado, ficam isso ao encargo do “smart pointer”.

Considerações finais

Creio que passei bem o pensamento por traz desse assunto. Falar sobre RAII é, de certa forma, falar do obvio. Mas no fundo todo o design pattern é falar um pouco sobre o obvio, só que o obvio que a gente nunca usa….

Também estou criando coragem. Hoje estou escrevendo sobre RAII, mas daqui a pouco vou escrever sobre “smart pointers“. Esse sim será um assunto BEM mais interessante. Mas por enquanto, fica só como projeto do futuro.

E como final, se lembrem sempre:

  • Construtor => Você deve garantir que o objeto tenha seus atributos definidos e que todos os recursos sejam adquiridos/alocados.
  • Destrutor => Você deve garantir que todos os recursos que pertencem ao objeto sejam desalocados/liberados.

Esse sempre será o comportamento desejável, previsível e esperado de uma classe bem feita.


%d bloggers like this: