Pular para o conteúdo principal

Dependência cíclica de headers

Uma coisa que acaba atrapalhando quem está começando no C++ é a dependência cíclica dos headers. Quando estamos escrevendo programas minúsculos isso não acontece, mas é um cenário muito comum.

Antes de falar mais sobre as dependências, vamos entender um pouco da inclusão destes headers. Como funciona o famoso #include ?

Supondo que temos 3 arquivos: main.cpp, main.h e int_vector.h, com o seguinte conteúdo:



https://gist.github.com/thiagomg/00a5ecca0d29aa1997e1

Ao compilar o arquivo main.cpp, será feito um pré-processamento de macros (como o #include) e será gerado um arquivo de saída como este abaixo.

https://gist.github.com/thiagomg/3a500e045844e4a4c0c0

Então, o que seria a dependência cíclica ?

Conforme vimos, o pré-processador junta todos os arquivos referenciados em uma diretiva #include em um arquivo só. Quando um header inclui um outro header e este outro inclui o primeiro, o que o pré-processador deve fazer ?



Temos uma solução fácil para isso, mas antes vamos imaginar o seguinte cenário:
Nós temos uma main_window que pode ter uma janela filha e a main_window vai fechar a janela.

Vamos ver uma implementação inicial desse cenário

https://gist.github.com/thiagomg/1f899766ba4fbdea5cb8

Simples e direto. A main_window invoca o window::close.

Porém, vamos supor que o fechamento dessa janela pode ser impedido, ou seja, o window::close talvez não seja bem sucedido e este processamento é assíncrono.

Para isso, é necessário alertar o main_window que a janela foi fechada com sucesso.

https://gist.github.com/thiagomg/1fc8fbae08a7f74f30c6

Neste caso, é preciso que o main_window conheça o window, uma vez que vai ser invocado o close, mas também é preciso que o window conheça o main_window, já que ao ser invocado, ele vai alertar que window::close foi bem sucedido.

Uma forma inicial é:

https://gist.github.com/thiagomg/9a22c15e33e1c9c82692

Isso gera o problema de dependência cíclica, já que temos:

  • main_window.h incluindo o window.h

  • window.h incluindo o main_window.h

  • E assim indo sucessivamente, para todo o sempre.




Solução do problema

Para resolvermos este problema, precisamos definir um tipo incompleto (forward declaration na terminologia do C++)

https://gist.github.com/thiagomg/aab75a99fb5cd3daecbc

A linha struct main_window declara que uma classe incompleta main_window existe e será declarada posteriormente. Por ser um tipo incompleto, o compilador não conhece o seu tamanho, então somente temos esse tipo incompleto na forma de ponteiro ou referência.
Ex: main_window &_parent ou *_parent

Na implementação é preciso fazer o include do window.h para que este saiba onde encontrar o layout da classe incompleta usada

https://gist.github.com/thiagomg/12c80f9116231502e210

Essa forma de declaração é necessária devido ao C++ usar value_type por padrão e pela forma como a struct/classe fica em memória. Para o post não ficar muito extenso, isso será explicado em um próximo artigo. :-)

Fonte: https://github.com/SimplyCpp/examples/tree/master/dependencia

Comentários

Postar um comentário

Postagens mais visitadas deste blog

Mestre Iota

Iota é a nona letra do alfabeto grego, ela é equivalente à letra i do nosso alfabeto. Por convenção ou hábito, utilizamos a letra i na programação para indicar algum tipo de incrementador, como por exemplo, em um for-loop . https://gist.github.com/fabiogaluppo/a23894ae743f7dd29274 Curiosamente, iota , como identificador, também é utilizado na programação para indicar uma sequência finita e consecutiva de números inteiros, como por exemplo, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. Inclusive, originalmente na STL existia a função iota , inspirada pela linguagem de programação APL , você pode conferir neste link: http://www.sgi.com/tech/stl/iota.html

Valores Aleatórios Simplificados

A partir do C++ 11, foi introduzido o header <random>  com diversos facilitadores para suporte de geração de números aleatórios. A produção destes números é feita através da combinação de duas categorias de objetos: os geradores e os distribuidores. Os geradores, são responsáveis pela geração dos números, e os distribuidores são responsáveis pela transformação dos números gerados em algum tipo de distribuição de probabilidade.  Como por exemplo, uma distribuição normal (aquela da Gaussiana) ou uma distribuição de Pareto (aquela do 80-20). As opções não faltam, como você pode ver nas referências, por exemplo:   http://www.cplusplus.com/reference/random/ ou  http://en.cppreference.com/w/cpp/header/random .

Policy-based design: log writer

Policy-based design Vamos neste artigo dar mais uma pincelada no Policy-based design . Vamos fazer como exemplo uma classe de log. Como este é só um exemplo, não vamos considerar múltiplos parâmetros no log, mas somente uma string, assim não fugiremos do assunto. Uma das coisas mais importantes neste tipo de design é o desacoplamento. Ele é uma excelente alternativa ao uso de interfaces por duas razões: Não gera chamadas virtuais (ou um nível de indireção em tempo de execução) Duck typing ( https://pt.wikipedia.org/wiki/Duck_typing ) Eu gosto bastante desse tipo de design, já usado aqui: http://simplycpp.com/2016/02/05/leitura-de-configuracao-em-c/