[c++] includes en classes: onoverzichtelijk

Pagina: 1
Acties:

  • MisterData
  • Registratie: September 2001
  • Laatst online: 26-08 21:52
Ik ben nu net een tijdje bezig met C++, en ben al begonnen met het maken van een aantal leuke programmaatjes. Nu zit ik echter met een probleempje. Ik heb namelijk een hoop classes (13) en die includen elkaar allemaal. Ik heb natuurlijk wel om al mijn class-declaraties de bekende #define-#ifndef constructie staan, maar toch gaat de compiler zeuren over classes die niet gevonden worden en classes die allang ge-include zijn. Hoe krijg ik het nou voorelkaar dat 3 of meer bestanden elkaar zonder problemen kunnen includen? Het gaat er dus vooral om dat bijv. class A een member heeft met type class C, die weer een member class A heeft, enzovoort. Hoe lossen jullie dit meestal op?

Ja, ik heb de search gebruikt (ook die van ACM) en ik heb ook al geexperimenteerd met #pragma once's enzo..... ja ik ben wanhopig ;)

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Dan declare je alleen die class even, maar include je niet de hele header.

Dus stel class B heeft alleen maar een unctie die bv een ref naar class A nodig heeft, dan doe je in de header van B niet #include "a.h", maar: class A;

Dan weet je compiler dat je een class bedoeld als je A gebruikt.

Dit is vaak wel een oplossing al. En anders moet je misschien een iets ander ontwerp proberen...

#pragma is eeeevil. Gebruik die nooit, helemaal niet als je net begint.

  • MisterData
  • Registratie: September 2001
  • Laatst online: 26-08 21:52
Zoijar schreef op 16 oktober 2002 @ 19:22:
Dan declare je alleen die class even, maar include je niet de hele header.

Dus stel class B heeft alleen maar een unctie die bv een ref naar class A nodig heeft, dan doe je in de header van B niet #include "a.h", maar: class A;

Dan weet je compiler dat je een class bedoeld als je A gebruikt.

Dit is vaak wel een oplossing al. En anders moet je misschien een iets ander ontwerp proberen...
Dat had ik al geprobeerd, maar het wil nog steeds niet echt. Want als ik bijvoorbeeld een class A heb, en ik declare daarboven class B, dan gaat 'ie (GCC of MSVC, maakt niet uit) nog steeds zeuren.... het zou zoveel makkelijker zijn als er niet een tooltje was dat even voor mij de volgorde van includes op een rij zou kunnen zetten, dan weet je tenminste waneer je wel en niet moet includen. Ander ontwerp heb ik natuurlijk liever niet :(
#pragma is eeeevil. Gebruik die nooit, helemaal niet als je net begint.
Weet ik :) De compiler begon al te zeuren over dat #pragma obsolete is, dus heb het maar gelijk weggehaald :) Daarnaast is het natuurlijk compiler-afhankelijk.

  • MisterData
  • Registratie: September 2001
  • Laatst online: 26-08 21:52
code:
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
#ifndef __DDENGINE_TRIANGLE_H
#define __DDENGINE_TRIANGLE_H

class Vertex;
class Object;
#include "Vertex.h"

class Triangle {
    public:
        Triangle();
        Triangle(Vertex a,Vertex b,Vertex c);

        void clipFrustrum(int w,int h);
        void project(Matrix normalProjection);
        void RegenerateNormal();
        Vector GetWeightedNormal();
        Vertex GetMedium();
        Vector GetCenter();
        float GetDist();
        bool Degenerated();
        Triangle GetClone();


        Object* parent;
        bool visible;
        bool outOfFrustrum;
        Vertex p1;
        Vertex p2;
        Vertex p3;
        Vector n;
        Vector n2;
        int minx,maxx,miny,maxy;
        Vector triangleCenter;
        float dist;
        int id;
        virtual ~Triangle();

};

#endif


resultaat:

code:
1
Lijn 27: Triangle.h: field p1 has incomplete type


:'(

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 11:37

.oisyn

Moderator Devschuur®

Demotivational Speaker

de klassen waarvan de definitie niet nodig is zijn pointers, references en klassen als parameters

in je bovenstaande code is het dus van belang dat de compiler de inhoud van Vertex en Vector weet, maar bijvoorbeeld niet voor Object en Matrix.

Dit zou dus moeten werken:

C++:
1
2
3
4
5
6
7
8
9
10
#include "Vector.h"
#include "Vertex.h"

class Object;
class Matrix;

class Triangle
{
    ...
};


gewoon altijd op deze manier te werk gaan, en je hebt geen problemen (en bovendien zo klein mogelijke compiletijden bij veranderingen :))

Ik neem aan dat bijvoorbeeld Vertex de klasse Vector moet kennen, maar dit zou voor de klasse Triangle niet uit moeten maken! Als ik de 2 bovenstaande includes om zou draaien dan hoort er nog geen probleem te zijn, aangezien Vertex.h ook gewoon Vector.h include


PS. het is trouwens Frustum, niet Frustrum ;)
Bovendien heeft een view frustum wat meer parameters dan alleen breedte en hoogte (wat jij doet is viewport clipping)
Ik zie trouwens ook dat er niets wordt teruggegeven, terwijl een driehoek bij clipping geen driehoek hoeft te blijven, maar ook een vier of vijfhoek kan worden (maw, je kunt meer driehoeken terug krijgen dan je erin stopt)

Nog een opmerkinkje: het returnen van objecten kan nogal wat temporary objects met zich meebrengen... is op zich niet zo'n probleem, maar nutteloze overhead is nou net iets wat je niet wil hebben in high speed applicaties :). Gebruik in plaats van returnwaarden gewoon een reference naar waar het resultaat moet staan als parameter. Triangle Triangle::GetClone () wordt in jouw geval dus void Triangle::GetClone (Triangle & result)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MisterData
  • Registratie: September 2001
  • Laatst online: 26-08 21:52
.oisyn schreef op 16 oktober 2002 @ 19:41:
de klassen waarvan de definitie niet nodig is zijn pointers, references en klassen als parameters

in je bovenstaande code is het dus van belang dat de compiler de inhoud van Vertex en Vector weet, maar bijvoorbeeld niet voor Object en Matrix.

Dit zou dus moeten werken:

C++:
1
2
3
4
5
6
7
8
9
10
#include "Vector.h"
#include "Vertex.h"

class Object;
class Matrix;

class Triangle
{
    ...
};


gewoon altijd op deze manier te werk gaan, en je hebt geen problemen (en bovendien zo klein mogelijke compiletijden bij veranderingen :))

Ik neem aan dat bijvoorbeeld Vertex de klasse Vector moet kennen, maar dit zou voor de klasse Triangle niet uit moeten maken! Als ik de 2 bovenstaande includes om zou draaien dan hoort er nog geen probleem te zijn, aangezien Vertex.h ook gewoon Vector.h include


PS. het is trouwens Frustum, niet Frustrum ;)
Bovendien heeft een view frustum wat meer parameters dan alleen breedte en hoogte (wat jij doet is viewport clipping)
Ik zie trouwens ook dat er niets wordt teruggegeven, terwijl een driehoek bij clipping geen driehoek hoeft te blijven, maar ook een vier of vijfhoek kan worden (maw, je kunt meer driehoeken terug krijgen dan je erin stopt)
* MisterData fluistert: ik ben dit aan het porten vanuit java idx3d, leek me niet al te moeilijk, dus vraag maar niks inhoudelijks :X :P

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 11:37

.oisyn

Moderator Devschuur®

Demotivational Speaker

MisterData schreef op 16 oktober 2002 @ 19:51:
[...]


* MisterData fluistert: ik ben dit aan het porten vanuit java idx3d, leek me niet al te moeilijk, dus vraag maar niks inhoudelijks :# :P


wheehehe lol :P
maar porten van java naar c++ in dit soort gevallen is lastiger dan je denkt hoor, vergis je daar niet in (waar je in java alles alloceerd met new en het automatisch wordt verwijderd moet je daar in C++ zelf voor zorgen. Alles converteren naar pointers werkt wel, maar dan zit je weer met memory leaks, en alles als instanties geeft weer problemen met klassen die data moeten sharen)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MisterData
  • Registratie: September 2001
  • Laatst online: 26-08 21:52
Bedankt voor de tip die je nog in je bericht hebt ge-edit :) Dit soort dingen zijn inderdaad belangrijk, zeker als je (zoals ik) vanuit Java naar C++ overstapt :)

  • MisterData
  • Registratie: September 2001
  • Laatst online: 26-08 21:52
.oisyn schreef op 16 oktober 2002 @ 19:54:

[...]


wheehehe lol :P
maar porten van java naar c++ in dit soort gevallen is lastiger dan je denkt hoor, vergis je daar niet in (waar je in java alles alloceerd met new en het automatisch wordt verwijderd moet je daar in C++ zelf voor zorgen. Alles converteren naar pointers werkt wel, maar dan zit je weer met memory leaks, en alles als instanties geeft weer problemen met klassen die data moeten sharen)
Ben al een heel eind :) Als het niet werkt is het nog geen probleem, dan heb ik toch al een hoop geleerd (zoals dit topic natuurljik) :) Kwestie van goed kijken of je een pointer moet gebruiken ergens of dat je gewoon een instantie kan gebruiken. Het is niet makkelijk nee, maar wel leerzaam dus ;)

  • MisterData
  • Registratie: September 2001
  • Laatst online: 26-08 21:52
Bovendien heb ik volgende week vakantie, en dus gigantisch veel tijd over om me es goed in C++ te verdiepen ;)

  • MisterData
  • Registratie: September 2001
  • Laatst online: 26-08 21:52
Volgende probleem:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef __DDENGINE_EDGE_H
#define __DDENGINE_EDGE_H

class Vertex;

class Edge {
    public:
        Edge();
        Edge(Vertex v1,Vertex v2);
        virtual ~Edge();
        Vertex a,b;
        Vertex start();
        Vertex end();
};

#endif


en dan zegt GCC:

code:
1
14: Z:\ddengine\Edge.h : field 'b' has incomplete type


:(

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 11:37

.oisyn

Moderator Devschuur®

Demotivational Speaker

nog even een algemeen implementatie detail :)

Triangles zijn technisch gezien niet interessant om in aparte objecten onder te brengen. Het geeft gewoon simpelweg te veel overhead (je moet ze allemaal apart aflopen om te renderen, enzovoorts). Zelfs Java3D, die vrij puristisch OO is obgebouwd, bevat geen klasse Triangle :)

In plaats daarvan moet je op model-level je klassen opbouwen. Een model bestaat uit triangles en vertices, en heeft daarom ook 2 buffers. Een vertexbuffer en een indexbuffer. In de vertexbuffer staan alle vertices die door de triangles gebruikt worden (is dus een array van vertices). Vrijwel alle vertices worden door meer dan 1 triangle gebruikt, maar die staan er niet dubbel in. De indexbuffer is simpelweg een array van integers, waar de indices van de triangles in staan.

Dit is ook de manier waarop 3D hardware werkt, en daardoor kan het dus in een keer door gegeven worden aan de driver die het vervolgens naar de videokaart stuurt. Rete-snel dus, en niet memory-consuming :)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 11:37

.oisyn

Moderator Devschuur®

Demotivational Speaker

MisterData schreef op 16 oktober 2002 @ 20:08:
Volgende probleem:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef __DDENGINE_EDGE_H
#define __DDENGINE_EDGE_H

class Vertex;

class Edge {
    public:
        Edge();
        Edge(Vertex v1,Vertex v2);
        virtual ~Edge();
        Vertex a,b;
        Vertex start();
        Vertex end();
};

#endif


en dan zegt GCC:

code:
1
14: Z:\ddengine\Edge.h : field 'b' has incomplete type


:(


Klopt, Vertex wordt hier niet gebruikt als reference, pointer of functie-parameter, maar als member. Hier is het dus noodzaak om Vertex.h te includen

Trouwens, in een Edge zou een referentie of een pointer naar een Vertex moeten staan, niet de Vertex zelf. Anders zouden 2 edges die aan elkaar vast zitten niet dezelfde Vertex kunnen delen (dan is het slechts een kopie)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MisterData
  • Registratie: September 2001
  • Laatst online: 26-08 21:52
.oisyn schreef op 16 oktober 2002 @ 20:13:
nog even een algemeen implementatie detail :)

Triangles zijn technisch gezien niet interessant om in aparte objecten onder te brengen. Het geeft gewoon simpelweg te veel overhead (je moet ze allemaal apart aflopen om te renderen, enzovoorts). Zelfs Java3D, die vrij puristisch OO is obgebouwd, bevat geen klasse Triangle :)

In plaats daarvan moet je op model-level je klassen opbouwen. Een model bestaat uit triangles en vertices, en heeft daarom ook 2 buffers. Een vertexbuffer en een indexbuffer. In de vertexbuffer staan alle vertices die door de triangles gebruikt worden (is dus een array van vertices). Vrijwel alle vertices worden door meer dan 1 triangle gebruikt, maar die staan er niet dubbel in. De indexbuffer is simpelweg een array van integers, waar de indices van de triangles in staan.

Dit is ook de manier waarop 3D hardware werkt, en daardoor kan het dus in een keer door gegeven worden aan de driver die het vervolgens naar de videokaart stuurt. Rete-snel dus, en niet memory-consuming :)
Ik zal het in m'n achterhoofd houden, maar het maakt mij niet echt uit of het nou iets minder snel is of niet. Ik heb hierboven namelijk al gezegd dat het vooral bedoeld is om C++ te leren kennen. Als ik daarna nog tijd heb dan zal ik eventueel de Triangle's eruit halen. Toch bedankt :*

  • MisterData
  • Registratie: September 2001
  • Laatst online: 26-08 21:52
.oisyn schreef op 16 oktober 2002 @ 20:15:

[...]


Klopt, Vertex wordt hier niet gebruikt als reference, pointer of functie-parameter, maar als member. Hier is het dus noodzaak om Vertex.h te includen

Trouwens, in een Edge zou een referentie of een pointer naar een Vertex moeten staan, niet de Vertex zelf. Anders zouden 2 edges die aan elkaar vast zitten niet dezelfde Vertex kunnen delen (dan is het slechts een kopie)
En door er een pointer van te maken heb ik het probleem dus ook opgelost :)

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

.oisyn schreef op 16 oktober 2002 @ 19:41:
de klassen waarvan de definitie niet nodig is zijn pointers, references en klassen als parameters

in je bovenstaande code is het dus van belang dat de compiler de inhoud van Vertex en Vector weet, maar bijvoorbeeld niet voor Object en Matrix.
Overigens is de rede hiervoor dat de grootte van die class niet bepaald kan worden. Bij een pointer is dat geen probleem, want die is altijd size 4 (32bit machine).

  • MisterData
  • Registratie: September 2001
  • Laatst online: 26-08 21:52
Zoijar schreef op 16 oktober 2002 @ 20:55:
[...]


Overigens is de rede hiervoor dat de grootte van die class niet bepaald kan worden. Bij een pointer is dat geen probleem, want die is altijd size 4 (32bit machine).
Ow dus dat is ook de reden waarom die 64-bit processoren zoveel krachtiger zijn? Ze kunnen gewoon meer geheugen aanspreken ofzo?

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

uhh nee, waar haal je dat nou uit? :-)

Ik bedoel als je dit doet:

class A;
class B {
A a;
};

Wat is dan sizeof( B ) ? Dat weet je niet omdat je sizeof(A) niet weet, daar kan immers van alles instaan.
Maar als je nu doet:

class A;
class B {
A* a;
};

dan is sizeof( B ) ineens (waarschijnlijk) 4, en dit kan dus wel.

edit:
en verder bedoelde ik met die 32bit opmerking, dat op een 64bit machine een pointer waarschijnlijk size 8 is

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 13-08 16:46

curry684

left part of the evil twins

Zoijar schreef op 16 oktober 2002 @ 19:22:
#pragma is eeeevil. Gebruik die nooit, helemaal niet als je net begint.
Mwah..... ik citeer uit een stukje code dat ik toevallig open heb staan:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
#ifdef WIN32
  #include <winsock2.h>

  #ifdef _MSC_VER
    #pragma comment(lib, "ws2_32")
  #else if defined __BORLANDC__
    #pragma link "ws2_32"
  #endif
  
  <...>
#else
  #error Socket encapsulation not implemented for current compilation platform
#endif

Deze pragma's schelen iig voor de Borland en VS developers (99.9% of zo dus) een hoop geetter met library dependencies instellen als ze deze lib gebruiken, omdat met dank aan de pragma automatisch de correcte winsock lib meegelinkt wordt.

Tevens zijn de volgende pragmas erg handig/nuttig/essentieel als je custom libs ontwikkelt:
C++:
1
2
3
4
5
#pragma init_seg({ compiler | lib | user | "section-name" [, func-name]} )
#pragma pack( [ show ] | [ push | pop ] [, identifier ] , n  )
#pragma warning( warning-specifier : warning-number-list [; warning-specifier : warning-number-list...] )
#pragma warning( push[ ,n ] )
#pragma warning( pop )

De eerste 2 kunnen zelfs dodelijk zijn als je ze NIET gebruikt. Zo kan ik nog wel even doorgaan, maar ik denk dat je de essentie wel doorhebt: Pragma's *kunnen* eeeevil zijn als je niet weet wat je doet, maar ze kunnen het leven ook een stuk aangenamer maken.

Professionele website nodig?


  • MisterData
  • Registratie: September 2001
  • Laatst online: 26-08 21:52
Zoijar schreef op 16 oktober 2002 @ 21:10:
uhh nee, waar haal je dat nou uit? :-)
Ik lul uit m'n nek dus 8)7
Ik bedoel als je dit doet:

class A;
class B {
A a;
};

Wat is dan sizeof( B ) ? Dat weet je niet omdat je sizeof(A) niet weet, daar kan immers van alles instaan.
Maar als je nu doet:

class A;
class B {
A* a;
};

dan is sizeof( B ) ineens (waarschijnlijk) 4, en dit kan dus wel.
Ik snap het.
edit:
en verder bedoelde ik met die 32bit opmerking, dat op een 64bit machine een pointer waarschijnlijk size 8 is
Naja dat is dit verhaal niet zo belangrijk :)

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

curry684 schreef op 17 oktober 2002 @ 02:56:
De eerste 2 kunnen zelfs dodelijk zijn als je ze NIET gebruikt. Zo kan ik nog wel even doorgaan, maar ik denk dat je de essentie wel doorhebt: Pragma's *kunnen* eeeevil zijn als je niet weet wat je doet, maar ze kunnen het leven ook een stuk aangenamer maken.
Het probleem zit hem dus in dit stukje:
16.6 Pragma directive
1 A preprocessing directive of the form # pragma pptokensopt newline causes the implementation to behave in an implementation defined manner. Any pragma that is not recognized by the implementation is ignored.
Jouw #pragma pack kan dus wel is helemaal niet doen wat je denkt dat het doet. Misschien doet het zelfs wel helemaal niets, en dan crashed je programma gebasseerd op welke compiler is gebruikt. Die library mensen weten vaak dat het een library wordt voor een specifieke compiler, dan kan het wel. Maar in algemene code dus liever niet. Ik heb de essentie wel door ja, maar denk er gewoon anders over :P

  • curry684
  • Registratie: Juni 2000
  • Laatst online: 13-08 16:46

curry684

left part of the evil twins

Zoijar schreef op 17 oktober 2002 @ 11:27:
Jouw #pragma pack kan dus wel is helemaal niet doen wat je denkt dat het doet. Misschien doet het zelfs wel helemaal niets, en dan crashed je programma gebasseerd op welke compiler is gebruikt. Die library mensen weten vaak dat het een library wordt voor een specifieke compiler, dan kan het wel. Maar in algemene code dus liever niet. Ik heb de essentie wel door ja, maar denk er gewoon anders over :P
Waarom dacht je dat ik heel specifiek die platform- en compileridentificatie #ifdefs mee had gekopieerd? :z

De stelling blijft dus onverminderd sterk: Pragma's *kunnen* eeeevil zijn als je niet weet wat je doet, maar ze kunnen het leven ook een stuk aangenamer maken. :P

Professionele website nodig?


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Maar met jouw stelling klinkt het net alsof het voor normale code geen probleem is om pragmas te gebruiken als je maar weet wat ze doen. En ik zie het ook bij zoveel mensen gebeuren, pragma-tje hier, pragma-tje daar...hehe. Maar ok we begrijpen elkaar wel geloof ik :)

  • MisterData
  • Registratie: September 2001
  • Laatst online: 26-08 21:52
Zoijar schreef op 17 oktober 2002 @ 11:44:
Maar met jouw stelling klinkt het net alsof het voor normale code geen probleem is om pragmas te gebruiken als je maar weet wat ze doen. En ik zie het ook bij zoveel mensen gebeuren, pragma-tje hier, pragma-tje daar...hehe. Maar ok we begrijpen elkaar wel geloof ik :)
Denk et ook wel ja ;)
Pagina: 1