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:

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
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
[…] Seguindo o post de dependências entre headers (Dependência cíclica de headers). […]
ResponderExcluir