[C++] structuur klassen

Pagina: 1
Acties:

  • jos707
  • Registratie: December 2000
  • Laatst online: 28-04 22:44
Ik zit wat in de knoei met de structuur van mijn verschillende klassen.
Stel ik heb 3 klassen, klasse 1 roept klasse 2 op en klasse 2 roept op
zijn beurt klasse 3 op. Nu zou ik terug willen keren vanuit klasse 3 weer naar
klasse 1.
Ik zou dit kunnen oplossen door het object van klasse steeds mee te geven en
dan dit object gebruiken om een functie in klasse 1 terug aan te roepen.
Nu vind ik dit nogal een omslachtige manier en bovendien komt VStudio steeds
compileerfouten geven wanneer ik klasse header3 include in klasse1 omdat
in klasse header3 reeds klasse1 include.
Wat is eigenlijk de meest correcte manier om dit te doen ?

Ik heb er even een voorbeeldje bij verzonnen om de vraag wat duidelijker te maken.

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
 
class CDriver
{
 public:
 CCar m_car;

 void MakeAride()
  {
   m_car.Drive();
  }

 void Call911()
  {
  ...
  } 

};

//----------------------------------

class CCar
{
 CRoad m_road;

 void Drive()
  {
  StartCar();
  m_road.CheckRoad();
  }

};

//--------------------------------

Class CRoad
{

 void CheckRoad()
  {
   if (IsProblem()) 
   //Call911()        ->> ??? 
  }

};

[ Voor 5% gewijzigd door jos707 op 13-09-2005 21:43 ]


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Ik twijfel over je ontwerp, maar in ieder geval ben je op zoek naar C++ forward declaration

  • seamus21
  • Registratie: December 2001
  • Laatst online: 24-02-2018
Oke volgens mij kan je op 3 (nette) manieren methoden van andere klassen gebruiken binnen een klasse.

1. Zorg dat de klasse methodes erft van die andere klasse (inheritance)
2. Geef die andere klasse mee in je constructor of aan de methode waarin je die andere klassemethode wilt kunnen aanroepen
3. Maak in die methode een nieuwe instantie van die andere klasse en roep daarvan de methode aan

Als ik zou mogen kiezen zou ik altijd proberen mijn ontwerp zo te maken dat manier 1 zoveel mogelijk gebruikt wordt. En inderdaad als je methodes wilt aanroepen van bestaande objecten moet je die meegeven aan de klasse (constructor of methode).

Always shoot for the moon. Even if you miss you will land among the stars...


  • curry684
  • Registratie: Juni 2000
  • Laatst online: 30-04 19:21

curry684

left part of the evil twins

Nu ben ik geen fan van design patterns maar als je dat driepuntenlijstje serieus gebruikt tijdens development zou ik je toch eens aanraden de diepere implicaties van de concepten aggregation, inheritance en proxy classes nalezen. "Methode 1" is soms een goede oplossing, maar absoluut niet "zoveel mogelijk", dat is namelijk afhankelijk van context en designdoelen.

Professionele website nodig?


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Sterker nog: methode 1 gaat in de categorie "vermijden tenzij uit het design een duidelijke IS-A relatie blijkt". Inheritance is de sterkste vorm van coupling.

Sowieso schort er wel meer aan het ontwerp. CDriver::m_car? Geen huurauto's, geen eigenaren van twee auto's, en een auto kun je niet verkopen? CCar::m_Road? Elke car kan maar op 1 weg rijden? Terwijl de eigenaar van die Car ook maar 1 auto heeft? Dan kan elke CDriver dus ook maar over 1 road rijden, namelijk CDriver::m_car.m_road

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • jos707
  • Registratie: December 2000
  • Laatst online: 28-04 22:44
Zoijar schreef op dinsdag 13 september 2005 @ 21:47:
Ik twijfel over je ontwerp, maar in ieder geval ben je op zoek naar C++ forward declaration
Wel ik denk niet dat hiernaar op zoek ben, want in klasse 3 wil ik klasse 1 terug oproepen met dezelfde instance.
MSalters schreef op dinsdag 13 september 2005 @ 23:48:
Sterker nog: methode 1 gaat in de categorie "vermijden tenzij uit het design een duidelijke IS-A relatie blijkt". Inheritance is de sterkste vorm van coupling.

Sowieso schort er wel meer aan het ontwerp. CDriver::m_car? Geen huurauto's, geen eigenaren van twee auto's, en een auto kun je niet verkopen? CCar::m_Road? Elke car kan maar op 1 weg rijden? Terwijl de eigenaar van die Car ook maar 1 auto heeft? Dan kan elke CDriver dus ook maar over 1 road rijden, namelijk CDriver::m_car.m_road
Inheritance is hier niet echt van toepassing volgens mij omdat de verschillende klassen in aard weinig gemeenschappelijk hebben.
En ik weet dat het ontwerp in de ts niet helemaal klopt maar het was slechts een voorbeeld om aan te geven wat ik bedoelde.
Ik heb zitten denken aan gebruik van een function pointer die dan kan worden gebriukt als callback om terug naar de originele klasse refereren, maar of dit echt een goed idee is :?

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 30-04 19:21

curry684

left part of the evil twins

jos707 schreef op woensdag 14 september 2005 @ 01:19:
[...]

Wel ik denk niet dat hiernaar op zoek ben, want in klasse 3 wil ik klasse 1 terug oproepen met dezelfde instance.
Ik weet welzeker dat je daarnaar op zoek bent, want je compiler errors zijn het gevolg van een foutieve include-structuur die je met forward declaraties kunt oplossen :)

edit:
weet trouwens niet wat je met "dezelfde instance" bedoelt maar ik ben bang dat daar al een dieper probleem uit spreekt dan een includeprobleempje overigens
Inheritance is hier niet echt van toepassing volgens mij omdat de verschillende klassen in aard weinig gemeenschappelijk hebben.
Absoluut. Maar dat heeft meer van doen met je volgende zin:
En ik weet dat het ontwerp in de ts niet helemaal niet klopt maar het was slechts een voorbeeld om aan te geven wat ik bedoelde.
Dit voorbeeld is een puinzooi en aan de hand daarvan kunnen we je echt niets uitleggen. Beetje alsof we je moeten leren te fietsen op een melkblik waar een wiel aan de binnenkant van de deksel gemonteerd is ;)

Geef gewoon eens je originele class hierarchy en welk probleem je vervolgens mee vastloopt (en doe ons en jezelf een plezier en strip alle overbodige code en members voordat je 18354 regels code post :P )
Ik heb zitten denken aan gebruik van een function pointer die dan kan worden gebriukt als callback om terug naar de originele klasse refereren, maar of dit echt een goed idee is :?
Nee, dat is echt definitely zo enorm geen goed idee :Y)

[ Voor 6% gewijzigd door curry684 op 14-09-2005 02:27 ]

Professionele website nodig?


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 19:41
Om van een bepaalde variabele ( wat dat is een object natuurlijk gewoon ) gebruik te maken heb je een aantal mogelijkheden:

- Maak die variabele global
- Maak het een parameter van de functie van waaruit je em nodig hebt
- Maak het een member van een/de (super)class
- Heroverweeg je ontwerp. Het kan zijn dat je de verantwoordelijkheden van je klassen niet goed hebt neergezet.

Nou is het erg afhankelijk van je ontwerp wat nou de juiste keuze is. Het zal duidelijk zijn wat de meest aanbevolen opties zijn.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • jos707
  • Registratie: December 2000
  • Laatst online: 28-04 22:44
Ok thx voor de reacties, ja mijn c++ kennis is vrij beperkt en probeer gewoon te coden op de meest correcte
manier. Volgens mij leer ik op deze manier het best c++ schrijven.
Het voorbeeldje dat ik in de ts heb gezet suckt en ik had het er misschien beter niet
bijgezet.
Nu wat ik in werkelijkheid aan het maken ben is een game.
De structuur van mijn klassen komt op het volgende neer:
Ik heb een klasse CGame die als hoofdklasse dient, een klasse CObject die het te
manipuleren object voorstelt en een klasse CInput die input van toetsenbord/muis
controleert. De hoofdklasse heeft een aantal objecten waarvan één object als player wordt
uitgekozen. Dit player-object wordt doorgegeven aan de klasse CInput, in CInput wordt
dit object dan gebruikt om een aantal acties te doen zoals moveUp,moveDown,..
Nu dienen sommige acties die geregistreerd worden door de input niet te worden uitgevoerd
te worden door het player-object maar door een andere klasse zoals ResetSim of CreateNewPlayer,...
En het dus bij dat laatste waar ik vast zit.

Ik kan dit soort dingen niet goed uitleggen en heb er ook niet echt veel ervaring mee maar
ik hoop dat mijn voorbeeld duidelijk genoeg is.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
 
//elke klasse heeft ook een header file
//sim.cpp-----------------------------------
class CGame
{
public:
CObject* objecten[10];

 void DoTimeStep()
 {
 object[0]->CheckInput(object[0]);
 }

}

//object.cpp--------------------------

class CObject
{
void MoveUp()
{
//... 
}

}

//input.cpp---------------------------

class CInput
{

void CheckInput(CObject object)
{
 if (keys[VK_UP])
 {
  object.MoveUp();
 }
 if (keys[VK_SPACE])
 {
 // ResetSim();        -->???
 }
}

}

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 30-04 19:21

curry684

left part of the evil twins

En ResetSim zit in class CGame? :)

Ik mag trouwens hopen dat die parameter van CheckInput een CObject* of (const) CObject& moet zijn ipv dat je continu alles by-value doorgeeft.

In ieder geval hoort iedere CObject natuurlijk te weten in welke CGame instance hij zit, je zou namelijk theoretisch 5 games kunnen runnen op het moment. Valide oplossing zijn in dit geval een global pointer naar CGame, niet echt extreem netjes maar wel correct, of een Singleton, wat de meest correcte oplossing is om 1 enkele CGame te hebben die je altijd kan terugvinden, of simpelweg ieder object laten opslaan in welke CGame hij zit middels een pointer.

Afhankelijk van je ontwerp heeft de laatste optie in principe de voorkeur, of de Singleton. Maar de global kan ook gewoon als snelle doch werkende oplossing :)

Professionele website nodig?


  • jos707
  • Registratie: December 2000
  • Laatst online: 28-04 22:44
curry684 schreef op woensdag 14 september 2005 @ 10:34:
En ResetSim zit in class CGame? :)

Ik mag trouwens hopen dat die parameter van CheckInput een CObject* of (const) CObject& moet zijn ipv dat je continu alles by-value doorgeeft.

In ieder geval hoort iedere CObject natuurlijk te weten in welke CGame instance hij zit, je zou namelijk theoretisch 5 games kunnen runnen op het moment. Valide oplossing zijn in dit geval een global pointer naar CGame, niet echt extreem netjes maar wel correct, of een Singleton, wat de meest correcte oplossing is om 1 enkele CGame te hebben die je altijd kan terugvinden, of simpelweg ieder object laten opslaan in welke CGame hij zit middels een pointer.

Afhankelijk van je ontwerp heeft de laatste optie in principe de voorkeur, of de Singleton. Maar de global kan ook gewoon als snelle doch werkende oplossing :)
Ik heb altijd geleerd van altijd zo weinig mogelijk globals te gebruiken. Een Singleton zegt mij
wel vaag iets maar dit zal dan opzoek werk zijn om erachter te komen hoe ik het via deze
weg kan doen. De laatste aangehaalde oplossing dmv een pointer zou de meest ideale zijn maar
kan je mij zeggen wat je juist hiermee bedoelt ?

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 30-04 19:21

curry684

left part of the evil twins

jos707 schreef op woensdag 14 september 2005 @ 11:14:
[...]

Ik heb altijd geleerd van altijd zo weinig mogelijk globals te gebruiken.
Dat is correct :) Vandaar dat ik ook zeg dat die oplossing niet de voorkeur verdient.
Een Singleton zegt mij wel vaag iets maar dit zal dan opzoek werk zijn om erachter te komen hoe ik het via deze weg kan doen.
Aanzetje:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class CGame
{
public:
  static CGame* GetInstance()
  {
    if(!m_Instance)
      m_Instance = new CGame();
    return m_Instance;
  }

private:
  CGame()
  {
    // Initialize...
  }

  static CGame* m_Instance;
}

CGame::m_Instance = NULL;

Dit is het globale idee achter een singleton, en hiermee kun je vervolgens vanuit iedere class CGame::GetInstance() aanroepen om de huidige instance te verkrijgen van de class.
De laatste aangehaalde oplossing dmv een pointer zou de meest ideale zijn maar
kan je mij zeggen wat je juist hiermee bedoelt ?
Kan niet simpeler:
C++:
1
2
3
4
5
6
7
8
9
10
11
class CObject
{
public:
  CObject(CGame* p_Game)
  {
    m_Game = p_Game;
  }

private:
  CGame* m_Game;
}

Met references is het overigens allemaal wat netter, maar met pointers functioneert het ook.

Als je gaat multithreaden trouwens je static functies wel threadsafe maken en zo, of goed synchronizeren in de aanroepende code :)

[ Voor 6% gewijzigd door curry684 op 14-09-2005 11:26 ]

Professionele website nodig?


  • jos707
  • Registratie: December 2000
  • Laatst online: 28-04 22:44
Goed thx voor de uitleg, ik de laatste aangereikte oplossing proberen te implementeren.

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Kort gezegd: als je gaat multithreaden, doe dat dan met een bewijsbaar correct ontwerp. MT is geen hack die je op bestaande code kunt loslaten.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
jos707 schreef op woensdag 14 september 2005 @ 10:27:
De structuur van mijn klassen komt op het volgende neer:
Ik heb een klasse CGame die als hoofdklasse dient, een klasse CObject die het te
manipuleren object voorstelt en een klasse CInput die input van toetsenbord/muis
controleert. De hoofdklasse heeft een aantal objecten waarvan één object als player wordt
uitgekozen. Dit player-object wordt doorgegeven aan de klasse CInput, in CInput wordt
dit object dan gebruikt om een aantal acties te doen zoals moveUp,moveDown,..
Nu dienen sommige acties die geregistreerd worden door de input niet te worden uitgevoerd
te worden door het player-object maar door een andere klasse zoals ResetSim of CreateNewPlayer,...
En het dus bij dat laatste waar ik vast zit.
[/code]
Tja, daarvoor hebben we dus Design Patterns. Je bent gewoon het wiel opnieuw aan het uitvinden.

Je class CInput produceert Command Objecten. CInput stuurt deze door naar CPlayer, want CInput weet niets van de verwerking van Command objecten. CPlayer verwerkt de commando's die CPlayer kent, en stuurt de andere commando's door naar CGame. Die kan in een multiplayer het commando misschien doorsturen naar CTCPconnection. Het idee is dat elke schakel in de ketting alleen de volgende schakel kent.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 19:41
De eigenlijke 'designfout' zit em dus in de CInput klasse. Deze zorgt ervoor dat:

- De interface van CObject heel groot gaat worden. ( De naam suggereert iig dat heel veel klassen CObject als parent gaan krijgen )
- De klasse CInput waarschijnlijk tot 'God class' wordt gebombardeerd. Deze moet immer alle bewerkingen die je op een CObject kunt verzinnen kennen.

Een oplossing zoals MSalters al zei is een extra laag abstractie : de Command klasse

[ Voor 4% gewijzigd door farlane op 14-09-2005 15:08 ]

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • jos707
  • Registratie: December 2000
  • Laatst online: 28-04 22:44
Ik probeer de dingen altijd zo min mogelijk abstract te maken maar in dit geval
zou het de dingen duidelijker maken.
Dus als ik het goed begrijp moet ik een klasse toevoegen Control
die als buffer dient tussen CGame en CInput. De objecten worden dan niet
meer aangemaakt in CGame maar in CControl. Vanuit CControl zal dan CInput
worden aangesproken om te controleren of er valide input is, indien dit het
geval is zal CControl het player-object aanspreken om een bepaalde handeling
te doen.
code:
1
2
3
4
5
Dus iets in deze trend: CGame -> CControl <--> Cinput
                                     ^
                                     |
                                     v
                                   CObject

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Nee. Dan heb je alsnog een God class, CControl. Die moet alle objecten en alle commando's kennen. Gevolg: een onderhoudsnachtmerrie.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • curry684
  • Registratie: Juni 2000
  • Laatst online: 30-04 19:21

curry684

left part of the evil twins

Interessant leesvoer voor jos707:
Command Pattern.
Command Pattern.
Command Pattern.

Google is je vriend en zo ;)

Professionele website nodig?


  • jos707
  • Registratie: December 2000
  • Laatst online: 28-04 22:44
Ok ik check je linkjes verder na. thx.

  • riezebosch
  • Registratie: Oktober 2001
  • Laatst online: 23-04 14:31
En hier een (naar mijn mening) goede site met alle Patterns inclusief voorbeelden:
www.dofactory.com met hier het Command Pattern.

offtopic:
curry684: Hierboven zeg je dat je niet zo'n fan bent van Design Patterns. Kan je ook uitleggen waarom niet? Persoonlijk vind ik het namelijk prachtige dingen.


edit:
De site is gewoon soort van online versie van GoF-boek!

[ Voor 9% gewijzigd door riezebosch op 14-09-2005 21:16 ]

Canon EOS 400D + 18-55mm F3.5-5.6 + 50mm F1.8 II + 24-105 F4L + 430EX Speedlite + Crumpler Pretty Boy Back Pack


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Is een event systeem niet beter op zijn plaats? Dus met classes Handler, KeyboardHandler : Handler, Event, KeyEvent : Event, CObject : KeyboardHandler, virtual CObject::onKey(KeyEvent& e), KeyEvent e(VK_UP); e.fire();? Het nadeel is dat je functionaliteit nu binnen je CObject ligt, en niet daarbuiten. Aan de andere kant is het voordeel dat je functionaliteit impliciet binnen je CObject ligt...

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 30-04 19:21

curry684

left part of the evil twins

riezebosch schreef op woensdag 14 september 2005 @ 21:14:
offtopic:
curry684: Hierboven zeg je dat je niet zo'n fan bent van Design Patterns. Kan je ook uitleggen waarom niet? Persoonlijk vind ik het namelijk prachtige dingen.
Met design patterns an sich is niets mis. Sterker nog, het is onmogelijk om een goed programma te schrijven zonder design patterns te gebruiken. Dat is meteen m'n grootste bezwaar, design patterns is gewoon zwaar overhypte nonsens om een labeltje te plakken op methodieken die iedere programmeur bij z'n volle verstand ook kan bedenken.

Gebruik design patterns zoals ze bedoeld zijn: als een labeltje voor een techniek die het bespreken van code makkelijker maakt. Gebruik ze niet zoals merendeel van de slechte developers ze gebruikt om hun mankementen te compenseren: vantevoren met een dartbord een pattern uitkiezen en dan dat volgen zonder dat je een idee hebt wat je code doet.

Ik heb nog nooit een goede programmeur meegemaakt die voordat ie een stuk code ging schrijven hardop de rest van de mensen erop wees welk pattern hij ging inzetten ervoor. Een goede programmeur schrijft intuitief de goede code zonder aan patterns te denken, en kan vervolgens in de documentatie uitleggen waarom hij dat pattern heeft ingezet en alle anderen niet voldeden zodat de mindere goden het ook snappen.

edit:
in bovenstaand verhaal mag je 'programmeur' en 'code schrijven' trouwens vervangen door 'architect' en 'UML kalken' indien van toepassing

[ Voor 6% gewijzigd door curry684 op 14-09-2005 21:40 ]

Professionele website nodig?


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
curry684 schreef op woensdag 14 september 2005 @ 10:34:
In ieder geval hoort iedere CObject natuurlijk te weten in welke CGame instance hij zit, je zou namelijk theoretisch 5 games kunnen runnen op het moment.
Eigenlijk is dit een erg vreemde benadering. 5 games is ook 5 draaiende applicaties. Als je nou het geheugengebied van een instantie beschouwt als een applicatie object, dan is main( ) een method daarvan en zijn globals gewoon data members van het applicatieobject. Een class als CGame is dan onzinnig, want de members ervan zijn in feite globals.

De enige practische rechtvaardiging is CGame::CGame, de constructor dus.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein

Pagina: 1