Quello che ho trovato da tutti i tipi di prove è un errore: quando si hanno componenti con dipendenza circolare l'uno sull'altro (più in C ++ che in linguaggi dinamici come Python), si sta cercando un problema, quindi lo faccio sempre punto di definire sempre una stretta relazione gerarchica tra le classi.
In questo caso sembra che B sia una classe di livello più alto di A, quindi continuiamo così. Che ne dici di qualcosa di simile:
typedef interface struct; // in MSVC this is built into compiler
interface IOwnerOfA {
virtual int someFunctionCall() = 0;
}
class A {
IOwnerOfA* m_pOwner;
void DoSomething() { .... bunch of code; m_pOwner->someFunctionCall(); .... }
}
class B : private IOwnerOfA {
// yeah, cast is optional but I like them in these cases
B() : m_a( static_cast< IOwnerOfA* >( this ) {}
virtual int someFunctionCall() {
... provide specific functionality A needs;
and nothing else;
}
A m_a;
}
Quindi ora la gerarchia è IOwnerOfA < - A < --- B. Tutti conoscono l'interfaccia, B sa di A, A non sa nulla di B, quindi domani potresti avere C usando A senza dover fare eventuali modifiche in A.
Per quanto riguarda la tua domanda su come memorizzare una variabile membro o passare come parametro in DoSomething (), dipende solo da te. Se è solo un luogo isolato, probabilmente passerei a un parametro. Se la relazione tra classi è realmente che B è un "proprietario" di A e in A ha bisogno di richiamare in B (tramite l'interfaccia IOwnerOfA) in un numero di punti, quindi memorizzarlo come membro.
Un'altra alternativa da considerare (se si sta eseguendo una operazione in un unico punto) è invece di utilizzare interfacce e puntatori di classe, è possibile adottare un approccio funzionale e DoSomething () accetta std :: function come un tipo di parametro . Quindi utilizzare std :: bind () per passare in una funzione membro associata. Potrebbe sembrare un po 'folle, ma in realtà è piuttosto semplice (tranne che per gli errori del compilatore se si verifica qualcosa di sbagliato) e molto flessibile (una volta superati gli errori del compilatore)
Da aggiungere a ciò che @JTrana, ha commentato. Ci sono sicuramente dei casi in cui B possiede A e per qualsiasi motivo A ha bisogno di richiamare in B. Se è necessario, le uniche due opzioni che vedo utilizzano l'interfaccia (sì un livello extra di indiretto) e l'uso di tipi diretti (dipendenza circolare). ). Tra queste 2 scelte, la complessità extra dell'interfaccia è per me un vincitore. Tuttavia, questi casi veri sono pochi e lontani tra loro. La maggior parte delle volte potresti essere in grado di ridefinire la tua app in modo da non dover fare questo ciclo circolare.
Considerare se una parte di B che A ha bisogno possa essere convertita in una classe C più piccola:
class C { // contains some commonly used functionality
}
class A {
DoSomething( C* pCommonGuy) { .... }
}
class B {
A m_a;
C m_c;
}