[C++] Como resolver o Problema das classes interdependentes / Referencia Cruzada

12 respostas
D

Tenho duas que classes são interdependentes e simplesmente não parece possível fazer isso funcionar, pois se não declarei uma a outra não pode ser declarada e vice-versa

class UseTester {
public:
	typedef int Integer;

	void doubleInc() {
		UseTester::Integer i;
		Tester::count += 2;
	}
};

class Tester {
public:
	static UsingTest::Integer count;
	static void inc() {
		++count;
	}
};

int Tester::count = 0;

esse é um exemplo simples, onde o membro de dados [color=blue]count[/color] [color=orange]depende[/color] do tipo definido na classe [color=red]UseTester[/color] e uso do membro count na função membro [color=blue]doubleInc[/color] [color=orange]depende[/color] da definição da classe [color=red]Tester[/color].

Uma solução que naturalmente pode resolver o problema é unir as classes, nesse contexto parece natural, mas caso tenho classes de contextos diferentes mas ainda sim interdependentes surge um problema, como se tiver uma classe Percistencia e uma classe Caixa onde Percistencia depende da definição de caixa para utilização e Caixa utiliza membros de dados estáticos de Percistencia.

Não posso resolver o problema com pré-definição e prototipação, pois quando se fala na utilização de membros de dados o compilador precisa da definição completa do membro de dados.

[size=24]Qual a melhor forma de resolver esse problema?[/size]

12 Respostas

V
  1. Não usar static;
  2. Não definir tudo no .h;
  3. Entender a separação entre declaração e definição;
  4. Modelar corretamente o código.
W

Tirou as palavras da minha boca.

Esta muito estranho isso, soh ler o seu post pra saber. Interdependente significa dependente uma da outra, “contextos diferentes” pra mim soa como coisas que parecem nao precisar ser dependentes.

//Daniel

J

Com o uso de namespaces, não deixando o código jogado e mal escrito dessa forma.

E

Ora, em C++ é perfeitamente possível você usar classes recursivamente dependentes. O único problema é que quando uma classe é incompletamente definida, você só pode definir membros que sejam ponteiros ou smart pointers para essa classe, como fiz abaixo.

Além disso, você não deve usar o costume do Java de definir tudo na declaração da classe; você precisa separar a implementação e a declaração em arquivos diferentes.

class First;

class Second {
public:
    First* first;
	int anything;
	Second (First* f) { first = f; }
};

class First {
public:
	Second second;
	First () : second (this) {}
};

int main (int argc, char *argv[]) {
	First f;
}
V

entanglement:
Ora, em C++ é perfeitamente possível você usar classes recursivamente dependentes. O único problema é que quando uma classe é incompletamente definida, você só pode definir membros que sejam ponteiros ou smart pointers para essa classe, como fiz abaixo.

Além disso, você não deve usar o costume do Java de definir tudo na declaração da classe; você precisa separar a implementação e a declaração em arquivos diferentes.

O entanglement elaborou mais minha resposta (em especial os itens 2 e 3).
Desculpe responder tão brevemente, estava sem tempo.

Agora, o seu código ainda está mal estruturado (não sei se foi só para forçar o exemplo). Usar static dessa forma é bastante prejudicial, seja em C#, java ou C++. O ideal é que static esteja restrito a constantes e, no caso de métodos, métodos fábrica. Dificilmente você vai usar para outra coisa.

No caso de “modelar corretamente o código”, é o que te explicaram. Me parece que você está criando dependências onde elas não deveriam existir. No mínimo, essas classes estão compartilhando dados de maneira estranha. Nesse caso, é bom voltar a estudar um pouco de arquitetura, padrões e refatoração, para entender como você poderia fatorar melhor seu código.

D

Sem relação.

D

ViniGodoy:
entanglement:
Ora, em C++ é perfeitamente possível você usar classes recursivamente dependentes. O único problema é que quando uma classe é incompletamente definida, você só pode definir membros que sejam ponteiros ou smart pointers para essa classe, como fiz abaixo.

Além disso, você não deve usar o costume do Java de definir tudo na declaração da classe; você precisa separar a implementação e a declaração em arquivos diferentes.

O entanglement elaborou mais minha resposta (em especial os itens 2 e 3).
Desculpe responder tão brevemente, estava sem tempo.

Agora, o seu código ainda está mal estruturado (não sei se foi só para forçar o exemplo). Usar static dessa forma é bastante prejudicial, seja em C#, java ou C++. O ideal é que static esteja restrito a constantes e, no caso de métodos, métodos fábrica. Dificilmente você vai usar para outra coisa.

No caso de “modelar corretamente o código”, é o que te explicaram. Me parece que você está criando dependências onde elas não deveriam existir. No mínimo, essas classes estão compartilhando dados de maneira estranha. Nesse caso, é bom voltar a estudar um pouco de arquitetura, padrões e refatoração, para entender como você poderia fatorar melhor seu código.

Sim, escrevi o trecho de código assim para destacar o problema, no fim pareceu mais coerente agrupar o que é de comum utilização das classes em uma terceira, parece mais coerente e resolve o problema, mas infelizmente cria uma forte dependência. Tenho que remodelar esse conceito, muito obrigado pela ajuda de todos.

J

Sem relação.

Existe relação sim. Para você ter uma idéia, que tipo de problema você resolveria com a aplicação que postou aí?

nenhuma.

E

De qualquer maneira, em vez de ficarem se pegando dizendo que o programa X resolve o problema Y, eu simplesmente digo que o MEU exemplo de sintaxe não resolve problema nenhum, apenas ilustra que em C++ é perfeitamente possível ter duas classes que são recursivamente dependentes; a única restrição é que é necessário usar declarações incompletas de classes e a classe X que usar a declaração incompleta da classe Y não pode referenciá-la de forma alguma, exceto como um ponteiro ou um smart pointer para essa classe Y.
Note que uma declaração incompleta não pode nem indicar uma relação de herança. Portanto uma declaração incompleta pode ser:

class X;

mas não pode ser:

class X : public Z;

que indicaria que a classe X é uma subclasse de Z.
C++ não permite tal tipo de sintaxe, provavelmente porque abriria alguma lata de minhocas que não consigo nem imaginar.

J

entanglement:
De qualquer maneira, em vez de ficarem se pegando dizendo que o programa X resolve o problema Y, eu simplesmente digo que o MEU exemplo de sintaxe não resolve problema nenhum, apenas ilustra que em C++ é perfeitamente possível ter duas classes que são recursivamente dependentes; a única restrição é que é necessário usar declarações incompletas de classes e a classe X que usar a declaração incompleta da classe Y não pode referenciá-la de forma alguma, exceto como um ponteiro ou um smart pointer para essa classe Y.
Note que uma declaração incompleta não pode nem indicar uma relação de herança. Portanto uma declaração incompleta pode ser:

class X;

mas não pode ser:

class X : public Z;

que indicaria que a classe X é uma subclasse de Z.
C++ não permite tal tipo de sintaxe, provavelmente porque abriria alguma lata de minhocas que não consigo nem imaginar.

Eu não estou pegando com ninguém, só estou questionando. E o que você fez está correto declarando as classes implicitamente. Eu só questionei aquele código porque não possui aplicação nenhuma. Não vejo como alguma situação fizesse chegar nele.

D

Sem relação.

Existe relação sim. Para você ter uma idéia, que tipo de problema você resolveria com a aplicação que postou aí?

nenhuma.

Perdão, não fui claro, a utilização de namespaces e a limpeza do código não tem relação com o problema que tento evidenciar no tópico.

No problema em questão possuo uma classe Percistence e uma classe AboutItem, a classe Percistence tem funções de gravação e recuperação de AboutItem, AboutItem é uma classe responsável por retirar informações à partir de um membro de dados que é um ponteiro do tipo Item e outros membros de dados que influenciam na mineração das informações.
Dada uma operação de gravação da classe Percistence o Item correspondente está em memória, AboutItem juntamente em memória e operando sobre o item, assim a operação ocorre naturalmente.
No caso de uma operação de recuperação (contida na classe Percistence) AboutItem pode ser recuperado completamente, mas ainda sim depende da criação dinâmica de um Item com dados básicos.
Numa situação de gravação o Item apontado por um membro de AboutItem tem múltiplas utilizações além da persistência, daí possui um tempo de vida indefinido.
O tempo de vida do Item apontado por um membro de AboutItem recuperado é igual ao tempo de vida do próprio AboutItem, significa que AboutItem deve liberar a área de memória reservada para Item em seu destrutor.
Tomando como um todo, posso utilizar uma lista de ponteiros para os Item criados dinamicamente pela Percistence e estes devem ser utilizados na liberação de memória por suas "AboutItem"s correspondentes,
de início via que esta lista estática devia ser guardada em Percistence ou AboutItem (origem do problema no tópico) na qual me parecia mais coerente manter a lista em Percistence, por sua natureza estática e pela responsabilidade das criações,
numa segunda análise tomei como solução isolar a lista em uma terceira classe (também poderia ser feito tornando ela global).

Em minha última análise, abandonando um pouco da coerência e dando prioridade a economia de recursos utilizo um novo membro de dados em AboutItem que tem função de indicar se AboutItem é o único proprietário e utilizador do Item apontado.

D

entanglement:
De qualquer maneira, em vez de ficarem se pegando dizendo que o programa X resolve o problema Y, eu simplesmente digo que o MEU exemplo de sintaxe não resolve problema nenhum, apenas ilustra que em C++ é perfeitamente possível ter duas classes que são recursivamente dependentes; a única restrição é que é necessário usar declarações incompletas de classes e a classe X que usar a declaração incompleta da classe Y não pode referenciá-la de forma alguma, exceto como um ponteiro ou um smart pointer para essa classe Y.
Note que uma declaração incompleta não pode nem indicar uma relação de herança. Portanto uma declaração incompleta pode ser:

class X;

mas não pode ser:

class X : public Z;

que indicaria que a classe X é uma subclasse de Z.
C++ não permite tal tipo de sintaxe, provavelmente porque abriria alguma lata de minhocas que não consigo nem imaginar.

Muito Obrigado pela contribuição entanglement, já tinha conhecimento da possibilidade de utilização de declarações incompletas, infelizmente no problema que possuía, as utilizações de membros de dados estáticos exigia declarações completas por parte de ambas classes.
Concordo, o tratamento de problemas não deve ser tão específico, o aprendizado na solução de um pode ajudar muito na solução de similares, principalmente quando se fala dos recursos de uma linguagem de programação.

Criado 15 de outubro de 2012
Ultima resposta 20 de out. de 2012
Respostas 12
Participantes 5