Tag Archives: design pattern em c++

C/C++: Accessors(getters) e mutators(setters) de um jeito diferente

Para que serve mutators e accessors?

Quando temos algum atributo em um objeto, esse atributo pode ser acessado e modificado para descobrir o estado do objeto.

Só que com o tempo, pode se fazer necessário que você tenha um maior controle no acesso a estados do objeto, para ter ou caso você queira um encapsulamento maior(Cache? Controle de acesso? Se que sabe 😉 ).

Com relação aos dados, existem duas operações possíveis para o acesso a um atributo do objeto, você pode acessar ou mudar o atributo. Com isso, você pode inserir uma camada nessas duas operações para poder ter esse encapsulamento a mais.

Já que sabemos o que fazer, então temos uma decisão aqui para tomar: Como iremos fazer isso? Existem algumas formas de se fazer isso, mas existe uma que utiliza polimorfismo em C++ que é mais particularmente interessante.

Ah, para quem ainda não entendeu ainda, os getters e setters servem como um estilo de implementar os accessor e mutators.

Como fazer em C++?

Cenário, dado que temos uma classe chamada Teste_accessor_mutator, que tem um atributo chamado _data. Esse atributo tem que ter um accessor chamado data e um mutator chamado data. Vamos ver isso em código implementado e um exemplo de acesso e mudança de atributo:

#include	<iostream>
#include	<cstdlib>

class Teste_accessor_mutator{
	private:
		int _data;
	public:
		const int &data(){
		return _data;
	}
	void data( const int &data ){
		_data=data;
	}
};

int main( int argc, char *argv[] ){
	Teste_accessor_mutator teste;

	teste.data(100);
	std::cout << teste.data() << std::endl ;
	return EXIT_SUCCESS;
} /* ---------- end of function main ---------- */

Pronto, com isso temos um getter e setter implementado! Perai, calma lá.

Porque eu usei _data como nome do atributo? Porque apesar de de C++ suportar polimorfismo, ele suporta polimorfismo entre métodos e não atributos. Como não vai ser usado o atributo a não ser internamente em raras ocasiões apenas colocar underline(_) na frente do atributo para mudar o nome não causa uma perda de visibilidade muito grande.

Além dessa alternativa, poderia ser qualquer outro nome no dado, como  por exemplo o atributo se chamar m_data. Mas esse estilo tem uma visibilidade boa e não difere do original, então é bem aceitável.

Porque não usar um get_data e um set_data? Não que seja um estilo ruim, mas os nomes dos métodos ficam um pouco poluídos com essa abordagem. Se você tem a vantagem do polimorfismo, fica mais legível o código.

Advertisements

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: