Ir para o conteúdo
ou

Software livre Brasil

0 integrantes

Nenhum(a)

Pinguim Engenheiro

 Voltar a Pinguim Enge...
Tela cheia Sugerir um artigo

Funções Virtuais e Controle de Acesso - Parte 2

5 de Dezembro de 2010, 0:00 , por Software Livre Brasil - 0sem comentários ainda | Ninguém está seguindo este artigo ainda.
Visualizado 123 vezes
Continuando...

Bem, como prometido, ai está a continuação do artigo "

Funções Virtuais e

Controle de Acesso", embora estivesse muito ocupado, guardei um tempo para postar ele para vocẽs. Espero que gostem, ele traz uma ótima solução para o problema que deixei na parte 1 deste artigo, e digo mais, é um must-read para todos os progamadores de C/C++ (e a modestia em? XD).


Estudo de Caso: Uma boa alternativa

Foi visto então, que colocar as funções membro virtuais no escopo público, com a intenção de definir de forma simples tanto a interface quanto a implementação, não é aconselhável (veremos em breve porque os destrutores são uma excessão). Podemos então tentar nos espelhar em alguma referência de peso pelo seu design eficiente, que não poderia deixar de ser a STL. 

Analizando os números das funções virtuais na STL, vemos que ela possui:
  • 6 funções virtuais públicas, na qual compreende std::exception::what() e suas derivadas
  • 142 funções virtuais privadas
Curiosamente, há uma grande quantidade de funções virtuais privadas, certamente é uma caso a se analizar. Portanto, pode-se adaptar o mal pensado código anterior, para algo semelhante ao código abaixo (e tentar entender porque grande parte da STL segue uma abordagem assim):


Está nova abordagem tem alguns benefícios visíveis, conseguimos ter uma interface estável e não-virtual ao mesmo tempo que garantimos o trabalho de serem responsáveis pela implementação do comportamento polimórfico às funções virtuais não-públicas. Portanto, como as funções virtuais são os responsáveis pela implementação do comportamento polimórfico, é melhor evitar deixa-las customizar a interface herdada, que supostamente já é consistente.
Além do que já comentamos, temos ainda os seguintes benefícios:
  1. Com a melhor separação entre interface e implementação, nós vemos livres para definir cada uma das duas separadamente, não tendo portanto que se preocupar com qualquer compromisso de desenvolver técnicas para forçar que a interface e a implementação pareçam a mesma coisa.
  2. Agora temos uma classe base menos frágil a mudanças futuras. Estamos livres para mudar de opinião em relação a implementação, podemos até mesmo particionar a implementação em várias funções virtuais privadas (o que não seria possível no primeiro estudo de caso sem que adicionacemos trabalho para o usuário), e mesmo assim manter a interface intocada.
  3. Garantimos também, uma maior facilidade em qualquer refatoração futura do código, já que agora a implementação está bem mais flexível.
Mas alguns podem questionar as desvantagens do overhead gerado pela chamada de uma função extra devido a como fizemos a interface. Ná prática, não há nenhum custo de chamada de função extra, pois a maioria dos compiladores considerará a função membro definida dentro da classe como inline, ou pode-se usar a palavra protegida inline na declaração e assim definir uma implementação em outro lugar. A única complexidade adicional, será o tempo extra que se gastará programando, se é que podemos considerar “uma linha extra” como complexidade adicional.

Conseguimos portanto desenvolver uma interface sólida. As classes continuam tendo a mesma quantidade de funções membro públicas para os usuários aprenderem, e continuam tendo a mesma quantidade de funções virtuais para os programadores que derivem a partir desta classe aprenderem. Nem a interface nem a implementação ficarão mais complicadas.

Então, se você não precisar de uma separação mais completa entre a interface e a implementação, geralmente esse método será suficiente para as suas necessidades. Além disso, seria uma boa ideia considerar o método discutido como uma separação mínima e necessária no desenvolvimento dos novos códigos orientados a objeto que qualquer um venha a produzir.

O que concluímos é que agora conseguimos separar bem as coisas e isso é muito bom, sabemos então os benefícios de termos funções virtuais privadas, nos resta então saber quando elas devem ser públicas ou protegidas.

Privado x Público x Protegido?

A grande dica aqui é “Sempre que possível faça as funções virtuais privadas”. Sabemos que as funções virtuais existem para possibilitar o polimorfismo, e a não ser que haja a necessidade de chama-las diretamente no código das classes derivadas, não há nenhum motivo para que as funções sejam alguma coisa que não privadas. 

Uma excessão a essa dica, é que as vezes queremos chamar alguma função virtual da classe base em nossas classes derivadas, e nesse caso faz mais sentido declarar uma função virtual como protegida. Não sendo necessário nenhuma função virtual declarada no escopo público, se queremos manter boas práticas de programação em nossos projetos.

Destrutores: Um caso mais complexo

Obviamente, sempre que uma função membro (ou método) for implementada através da interface da classe base, ela terá comportamento polimórfico, portanto ela deve ser uma função virtual. Isto é verdade mesmo no nosso estudo de caso que separou melhor a interface da implementação, pois mesmo tendo uma interface não-virtual, nós delegamos o trabalho para as funções virtuais privadas para obtermos o polimorfismo desejado.
Consequentemente o destrutor não é uma excessão e deletar um objeto polimorficamente sem um destrutor virtual é um ato inconsequente, de comportamento totalmente imprevisivel.


 

Com o exemplo da figura acima, podemos ver que não é possível aplicar o nosso método do estudo de caso a um destrutor virtual, pois ser privado implicaria que o destrutor não seria visto nas classes que herdassem a sua própria classe. E está impossibilidade se deve ao fato de que os objetos são destruídos na ordem inversa ao de sua construção, com isto em mente, é fácil perceber que quando chegássemos no destrutor da classe base, não haveria mais nenhuma parte do objeto derivado existente e se o destrutor da interface da classe base chamasse uma função virtual, a v-table só teria entradas para as funções virtuais da classe base e nós não conseguiríamos o efeito polimórfico desejado, pois as partes abaixo da classe base, na hierarquia de herança, já teriam sido destruídos.
 
Perceba que o destrutor é o único exemplo em que o nosso método do estudo de caso não pode ser aplicado a uma função virtual.
 
Finalmente, e se quiséssemos que uma classe base fosse concreta (vulgo instanciada por sí mesma) mas também desejássemos ter um destrutor polimórfico? É verdade que seria necessário que o destrutor fosse público. O único problema é que derivar de classes concretas não é nem um pouco recomendável (pode até acontecer na prática, desde que não seja você que o faça ^^) e nesse caso, você realmente precisaria de um destrutor público, mas apenas devido a uma péssima escolha de design. Sendo melhor refatorar o código e evitar esse tipo de má prática de programação.
 
Resumindo, temos basicamente duas opções (que não se referem a classes base concretas lógico):

  1. Você deseja deletar polimorficamente através de um ponteiro base, que requer que o destrutor seja virtual e público.
  2. Você não deseja deletar polimorficamente através de um ponteiro base, caso em que o destrutor deve ser não-virtual e protegido, para evitar as deleções intencionais não autorizadas.
Recapitulando

Sempre que possível, prefira declarar funções virtuais em uma classe base como privada (ou protegida se realmente for necessário). Basicamente, isto garante que os objetivos da interface e da implementação estejam separados, o que estabiliza a interface e faz com que a implementação seja mais fácil de manter e refatorar. 

Para as funções de uma classe base normal, siga as seguintes instruções:

  1. Prefira fazer interfaces que não sejam virtuais.
  2. Prefira declarar funções virtuais como privadas.
  3. Se somente se as classes derivadas precisarem chamar a implementação da classe base, é que as funções virtuais devem ser protegidas.
E especificamente para o caso dos destrutores:
  1. Um destrutor de uma classe base deve ser ou público e virtual, ou protegido e não-virtual.
Bibliografia

[DEITEL, H. M.; DEITEL, P. J.] C++ : How to program; 7a. Edição
http://gotw.ca/publications/mill18.htmhttp://en.wikipedia.org/wiki/Virtual_method_table



Fonte: http://feedproxy.google.com/~r/OPinguimEngenheiro/~3/1s7gcMrLlx0/funcoes-virtuais-e-controle-de-acesso.html

0sem comentários ainda

Enviar um comentário

Os campos são obrigatórios.

Se você é um usuário registrado, pode se identificar e ser reconhecido automaticamente.