/ Forside / Teknologi / Udvikling / C/C++ / Nyhedsindlæg
Login
Glemt dit kodeord?
Brugernavn

Kodeord


Reklame
Top 10 brugere
C/C++
#NavnPoint
BertelBra.. 2425
pmbruun 695
Master_of.. 501
Bech_bb 500
kyllekylle 500
jdjespers.. 500
gibson 300
scootergr.. 300
molokyle 287
10  strarup 270
Container der rummer forskellige datatyper~
Fra : Janus


Dato : 10-05-07 13:32

Jeg er i gang med denne opgave ved diku
http://www.diku.dk/forskning/performance-engineering/Generic-programming/Assignments/assignment3.pdf

Den er sådan set let nok... bortset fra at jeg konstant løber ind i
problemet med ...

hvordan kan jeg have forskellige datatyper i SAMME container.. med
templates, og uden dynamisk polymorfi?
Jeg ville gerne have en vector med nogle objekter. Disse objekter er ikke af
samme type, og jeg kan ikke lagre dem som en supertype, som jeg normalt
ville gøre, da opgaven netop handler om at bruge templates istedet.

Det er lidt svært at forklare problemet yderligere uden at skulle skrive en
hel stil.
Kort sagt så vil jeg gerne have en container der rummer objekter af
forskellige klasser. Der skal så være en metode som løber containeren
igennem og kalder nogle metoder for de indeholdte objekter. Det er så nogle
metoder som alle klasserne har, så det giver mening at kalde dem, men jeg
kan ikke lave en container, som template, der rummer forskellige typer
objekter, uden polymorfi

Uden templates ville jeg gøre noget i stil med det her.. strengt forenklet..
men med templates synes jeg ikke jeg kan samle det i een container :-/
At have en mængde containers, en for hver objekttype, som man så skaltilgå
ved navn, det holder da slet ikke, men det er den eneste templatemåde jeg
kan komme på

class Geometry
{
public:
virtual bool Collide(Geometry* other)=0;};


class Box: public geometry
{
virtual bool Collide(Geometry* other){handle collision based on others
type......}};

class Sphere: public geometry
{
virtual bool Collide(Geometry& other){handle collision based on others
type......}
};

containeren er så:
vector<Geometry*> container;

og den gennemløbes som
for(int i=0;i<size;i++)
for(int a=0;a<size;a++)
if(i!=a) container[i].Collide(container[a]);






 
 
Jakob Bøhm (10-05-2007)
Kommentar
Fra : Jakob Bøhm


Dato : 10-05-07 13:52

Janus wrote:
> Jeg er i gang med denne opgave ved diku
> http://www.diku.dk/forskning/performance-engineering/Generic-programming/Assignments/assignment3.pdf
>
> Den er sådan set let nok... bortset fra at jeg konstant løber ind i
> problemet med ...
>
> hvordan kan jeg have forskellige datatyper i SAMME container.. med
> templates, og uden dynamisk polymorfi?
> Jeg ville gerne have en vector med nogle objekter. Disse objekter er ikke af
> samme type, og jeg kan ikke lagre dem som en supertype, som jeg normalt
> ville gøre, da opgaven netop handler om at bruge templates istedet.
>

Hvis du vil have data af flere typer i samme *instans* af en container,
og der ikke er en fast compile-time relation mellem placering i
containeren og datatype (som det kendes fra containertypen struct), så
er dette nærmest definitionen på dynamisk polymorfi. Selv med dynamisk
polymorfi kan der sagtens være masser af steder hvor templates kan
bruges til at indpakke de mange sjove downcasts der typisk skal bruges
til den slags.

Hvis du derimod vil have forskellige typer i forskellige instanser af
samme *klasse* er du ude i klassisk brug af templates, ligesom i
implementeringer af STL.

Held og lykke med din opgave!

--
Jakob Bøhm, M.Sc.Eng. * jb@danware.dk * direct tel:+45-45-90-25-33
Danware Data A/S * Bregnerodvej 127 * DK-3460 Birkerod * DENMARK
http://www.netop.com * tel:+45-45-90-25-25 * fax tel:+45-45-90-25-26
Information in this mail is hasty, not binding and may not be right

Thorsten Ottosen (10-05-2007)
Kommentar
Fra : Thorsten Ottosen


Dato : 10-05-07 17:18

Janus skrev:
> Jeg er i gang med denne opgave ved diku
>
http://www.diku.dk/forskning/performance-engineering/Generic-programming/Assignments/assignment3.pdf
>
> Den er s�dan set let nok... bortset fra at jeg konstant l�ber ind i
> problemet med ...
>
> hvordan kan jeg have forskellige datatyper i SAMME container.. med
> templates, og uden dynamisk polymorfi?
> Jeg ville gerne have en vector med nogle objekter. Disse objekter er
ikke af
> samme type, og jeg kan ikke lagre dem som en supertype, som jeg normalt
> ville g�re, da opgaven netop handler om at bruge templates istedet.

Se

http://www.boost.org/doc/html/variant.html

En anden løsning vil være

http://www.boost.org/doc/html/any.html

men den bruger polymorphi "under the hood". Den kan dog
optimeres væsenligt for en del typer:

http://www.codeproject.com/cpp/dynamic_typing.asp

mvh

Thorsten

Mogens Hansen (10-05-2007)
Kommentar
Fra : Mogens Hansen


Dato : 10-05-07 18:18


"Janus" <jan@no.mail.no> wrote in message
news:464310ae$0$7612$157c6196@dreader2.cybercity.dk...
> Jeg er i gang med denne opgave ved diku
> http://www.diku.dk/forskning/performance-engineering/Generic-programming/Assignments/assignment3.pdf
>

Der er nogle detaljer i den opgave beskrivelse som god kunne være lidt
bedre, når man tænker på at det er undervisningsmateriale.
F.eks. bryder jeg mig ikke om at funktione broad_base tager return
parameter - det virker som premature optimization, som fører til en anden
uheldig stil, nemlig at variablen "pairs" ikke initialiseres samtidig med
den erklæres.
I bedste fald er det en optimering der ikke er reel, på grund af RVO (return
value optimization).
Optimeringen bliver endnu mindre reel i den kommende C++ Standard, hvor der
inføres move constructors.

Jeg bryder mid ikke om at der er implementeret speciel run-time type
information, i stedet for at benytte dynamic_cast - med mindre der er
_rigtigt_ gode grunde til det, hvilket ikke er tilfældet.

>hvordan kan jeg have forskellige datatyper i SAMME container.. med
>templates, og uden dynamisk polymorfi?

Jeg tror ikke det er det du skal prøve på at gøre.

Hvis du alligevel vil kigge i den retning er
http://www.boost.org/doc/html/variant.html
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1939.html
http://www.boost.org/libs/tuple/doc/tuple_users_guide.html
http://www.open-std.org/jtc1/sc22/WG21/docs/papers/2002/n1403.pdf
udemærkede steder at kigge


>Jeg ville gerne have en vector med nogle objekter. Disse objekter er ikke
>af
>samme type, og jeg kan ikke lagre dem som en supertype, som jeg normalt
>ville gøre, da opgaven netop handler om at bruge templates istedet.

Det er (naturligvis) et klassisk spørgsmål, som går under navne som
"multimethod" og "dual dispatch".

Det ligger i problem domainet at du skal bruge runtime polymorfi (på een
eller anden form) til at afgøre om hvilke 2 typer objekterne har - det er jo
netop runtime dynamisk hvilke objekter der befinder sig på banen.

Du kan bruge compile-time polymorfi og template meta programmering til at
hjælpe dig med at generere dispatch funktionen.
Det er faktisk muligt at skrive noget template kode, som automatisk
genererer optimal hurtig kode.

I den udemærkede bog
Modern C++ Design, generic programming and design patterns applied
Andrei Alexandrescu
ISBN 0-201-70431-5
er der et helt kapitel som omhandler netop det emne

Det Design Pattern som hedder Visitor er også relateret til dette problem.

--
Venlig hilsen

Mogens Hansen



Janus (10-05-2007)
Kommentar
Fra : Janus


Dato : 10-05-07 20:52

> Der er nogle detaljer i den opgave beskrivelse som god kunne være lidt
> bedre, når man tænker på at det er undervisningsmateriale.

Nah. Det er jo ved DIKU, så det er faktisk ret høj standard. I det mindste
skal den ikke tilgås via en ssh tunnel til deres system, eller hentes på den
hemmelige opslagstavle nede i kælderen

> Jeg bryder mid ikke om at der er implementeret speciel run-time type
> information, i stedet for at benytte dynamic_cast - med mindre der er
> _rigtigt_ gode grunde til det, hvilket ikke er tilfældet.

Det undrede faktisk også mig.

> Det er (naturligvis) et klassisk spørgsmål, som går under navne som
> "multimethod" og "dual dispatch".

Det vil jeg læse op på. Takker for nøgleordene.

> Du kan bruge compile-time polymorfi og template meta programmering til at
> hjælpe dig med at generere dispatch funktionen.

Kan du give et kort eksempel på hvordan man griber det an? Jeg løber hele
tiden panden mod muren når jeg forsøger atlave et effektivt gennemløb af
flere containere med hver deres type, eller prøver at stoppe flere
forskellige objekter ind i samme container.

> Det er faktisk muligt at skrive noget template kode, som automatisk
> genererer optimal hurtig kode.
>
> I den udemærkede bog
> Modern C++ Design, generic programming and design patterns applied
> Andrei Alexandrescu
> ISBN 0-201-70431-5
> er der et helt kapitel som omhandler netop det emne

Også et link jeg vil følge. Jeg synes lige nu at templates er noget
/(&¤/"&"¤ besværligt noget og at man kan gøre tingene langt lettere og mere
elegant med god gammeldags runtimepolymorfi, men... noget siger mig at det
er værd at lære templates bedre at kende.

> Det Design Pattern som hedder Visitor er også relateret til dette problem.

Jah. Du ville lave en visitor til hver type geometri og så køre dem igennem?
Det kan jeg ikke helt se hvordan ville gavne hastigheden i forhold til
almindelige dynamisk polymorfi. Måske jeg misser pointen?

Den almindelige løsning jeg lavede, før templates, som stadig driller, er at
have en anstrakt base class med metoden
virtual bool CollidesWith(Geometry* other, Matrix4x4 t, Matrix4x4 r,
Matrix4x4 s)=0;

Så laver jeg konkrete klasser som Box, Sphere etc som arver fra den og skal
implementere metoden og håndtere kollision med andre objekter.
Jeg har en vector af Geometry hvor jeg for hvert element kalder
geometryObject.CollideWith(some other object) og det metodekald bliver
udført af den konkrete klasse. Det er een virtual metode. Det kan for dælen
ikke være så langsomt endda?

Jeg paster lige det hele ind her i bunden. Så kan du og andre kritisere det
sønder og sammen hvis I vil være så venlige.
Templateløsningen, as it, den er ikke værd at vise frem. Som sagt.. kritik
er meget velkommen, for lige nu er c++ er ikke lige mit sprog.

--------------------------------------------------------

#pragma once

#include <tchar.h>
#include <vector>

//the current types of geometry
enum GeometryType{UNDEFINED=0,BOX,SPHERE,MODEL3D};

//just a placeholder for a transformation matrix
struct Matrix4x4
{
int somethingWeWontNeedHere;
};


//abstract base class, which forces children to implement the CollidedWith
method
class Geometry
{
public:
virtual bool CollidesWith(Geometry* other, Matrix4x4 t, Matrix4x4 r,
Matrix4x4 s)=0;
GeometryType GetType(){return geometryType;}
protected:
GeometryType geometryType;
};

//scene elements contain some geometry, which can be shared with other
elements, and also
//contains transformation matrices used to move the , possibly, shared
geometry in the scene
class SceneElement
{
public:
SceneElement(Geometry* geometry){Geometry=geometry;}
Matrix4x4 Position;
Matrix4x4 Scaling;
Matrix4x4 Rotation;
Geometry* Geometry;
};





//scene which holds a collection of scene elements, and which handles the
dispatching
class Scene
{
public:
void DispatchGeometry()
{

for(unsigned int i0=0;i0<sceneElements.size();i0++)
{
SceneElement *a = sceneElements[i0];
for(unsigned int i1=i0+1;i1<sceneElements.size();i1++) //start counter
from previous i1+0
{
SceneElement *b = sceneElements[i1];
//check collisions by delegating to the gemetry and providing the
transformation matrices
if(a->Geometry->GetType()>b->Geometry->GetType())
a->Geometry->CollidesWith(b->Geometry, a->Position,
a->Rotation,a->Scaling);
else
b->Geometry->CollidesWith(a->Geometry, b->Position,
b->Rotation,b->Scaling);
}
}
}
void AddGeometry(SceneElement* newSceneElement)
{
sceneElements.push_back(newSceneElement);
}

std::vector<SceneElement*> sceneElements;
};

//a box geometry which knows about only boxes
class Box: public Geometry
{
public:
Box(){geometryType=BOX;}

virtual bool CollidesWith(Geometry* other, Matrix4x4 t, Matrix4x4 r,
Matrix4x4 s)
{
switch(other->GetType())
{
case BOX:
printf("BOX vs. BOX, resolved by BOX\n");
return true;
break;
default:
printf("BOX vs something new and unknown, resolved by BOX\n");
return false;//we dont know how to collide, so we don't
break;
}
}
};

//a sphere geometry which knows about boxes and spheres
class Sphere: public Geometry
{
public:
Sphere(){geometryType=SPHERE;}

virtual bool CollidesWith(Geometry* other, Matrix4x4 t, Matrix4x4 r,
Matrix4x4 s)
{
switch(other->GetType())
{
case BOX:
//transform geometry by matrices and do actual testing
printf("SPHERE vs. BOX, resolved by SPHERE\n");
return true;
break;
case SPHERE:
printf("SPHERE vs. SPHERE, resolved by SPHERE\n");
return true;
break;
default:
printf("SPHERE vs something new and unknown, resolved by SPHERE\n");
return false; //we dont know how to collide, so we don't
break;
}
}
};

//a model containing an arbitrary form
class Model3D: public Geometry
{
public:
Model3D(){geometryType=MODEL3D;}

virtual bool CollidesWith(Geometry* other, Matrix4x4 t, Matrix4x4 r,
Matrix4x4 s)
{
switch(other->GetType())
{
case BOX:
printf("MODEL3D vs. BOX, resolved by MODEL3D\n");
return true;
break;
case SPHERE:
printf("MODEL3D vs. SPHERE, resolved by MODEL3D\n");
return true;
break;
case MODEL3D:
printf("MODEL3D vs. MODEL3D, resolved by MODEL3D\n");
return true;
break;
default:
printf("MODEL3D vs something new and unknown, resolved by MODEL3D\n");
return false; //we dont know how to collide, so we don't
break;
}
}
};

//to add a new kind of geometry, simply declare a new decendant of Geometry
and implement the pure virtual method

------------------------------------------------

The test method used is

Scene scene;

//two baic objects are generated along with 2 complex models
Box box;
Sphere sphere;
Model3D car;
Model3D house;

//seven scene elements are generated using the three base geometry models
scene.AddGeometry(new SceneElement(&box));
scene.AddGeometry(new SceneElement(&sphere));
scene.AddGeometry(new SceneElement(&car)); //first element using the car
model
scene.AddGeometry(new SceneElement(&sphere));
scene.AddGeometry(new SceneElement(&house)); //first element using the
house model
scene.AddGeometry(new SceneElement(&car)); //second element using the car
model
scene.AddGeometry(new SceneElement(&house)); //second element using the
house model

scene.DispatchGeometry();





Mogens Hansen (10-05-2007)
Kommentar
Fra : Mogens Hansen


Dato : 10-05-07 21:12


"Janus" <jan@no.mail.no> wrote in message
news:464377e4$0$21928$157c6196@dreader1.cybercity.dk...

[8<8<8<]
>> Du kan bruge compile-time polymorfi og template meta programmering til at
>> hjælpe dig med at generere dispatch funktionen.
>
> Kan du give et kort eksempel på hvordan man griber det an? Jeg løber hele
> tiden panden mod muren når jeg forsøger atlave et effektivt gennemløb af
> flere containere med hver deres type, eller prøver at stoppe flere
> forskellige objekter ind i samme container.

Jamen det skal du heller ikke bruge.
Du skal bruge en container med pointere til bais-typen.

Man kan bruge template programmering til lave dispatch koden.
Et bud på det er bedre beskrevet i den bog jeg nævnte end man kort kan gøre
på en nyhedsgruppe.

[8<8<8<]
> Også et link jeg vil følge. Jeg synes lige nu at templates er noget
> /(&¤/"&"¤ besværligt noget

Det kan jeg egentlig godt forstå - man skal lige vænne sig til tankegangen.

> og at man kan gøre tingene langt lettere og mere elegant med god
> gammeldags runtimepolymorfi, men...

Begge former for polymorfi er relevant, og støtter faktisk hinanden rigtigt
godt.
Bogen
Multi-Paradigm Desgin for C++
James O. Coplien
ISBN 0-201-82467-1
beskriver fantastisk god hvordan de 2 former for polymorfi virker sammen, og
hvornår man bruger hvad.
Det er en temmelig svær bog at læse - men den er umagen værd.

> noget siger mig at det er værd at lære templates bedre at kende.

Helt afgjort.

>
>> Det Design Pattern som hedder Visitor er også relateret til dette
>> problem.
>
> Jah. Du ville lave en visitor til hver type geometri og så køre dem
> igennem?

Nej - det ville jeg ikke.
Men man kan betragte Visitor som en dual dispatch løsning:
Den kaldte funktion afhænger af 2 typer.

> Det kan jeg ikke helt se hvordan ville gavne hastigheden i forhold til
> almindelige dynamisk polymorfi.

Bestemmelsen af den rigtige funktion gøres i konstant, og rimelig kort tid i
Visitor.
Til gengæld har Visitor nogle uheldige egenskaber med hensyn til
afhængigheder og stabilitet overfor udvidelse af typerne, som var en del af
opgaven.

> Måske jeg misser pointen?
>
> Den almindelige løsning jeg lavede, før templates, som stadig driller, er
> at have en anstrakt base class med metoden
> virtual bool CollidesWith(Geometry* other, Matrix4x4 t, Matrix4x4 r,
> Matrix4x4 s)=0;

Umiddelbart vil jeg anbefale at bruge reference til Geometry, idet man
slipper for at kunne teste for om man faktisk har et objekt.
Desuden vil jeg overføre Matrix4x4 med const reference:
virtual bool CollidesWith(Geometry const& other, Matrix4x4 const& t, ...) =
0;

>
> Så laver jeg konkrete klasser som Box, Sphere etc som arver fra den og
> skal implementere metoden og håndtere kollision med andre objekter.
> Jeg har en vector af Geometry hvor jeg for hvert element kalder
> geometryObject.CollideWith(some other object) og det metodekald bliver
> udført af den konkrete klasse. Det er een virtual metode. Det kan for
> dælen ikke være så langsomt endda?

Nej det er ikke langsom - men du skal også lige finde ud af hvad den
konkrete type af other er

>
> Jeg paster lige det hele ind her i bunden. Så kan du og andre kritisere
> det sønder og sammen hvis I vil være så venlige.

Desværre - det er for stor en klump til jeg orker lige nu.
Måske andre har energien

--
Venlig hilsen

Mogens Hansen



Mogens Hansen (12-05-2007)
Kommentar
Fra : Mogens Hansen


Dato : 12-05-07 13:04


"Janus" <jan@no.mail.no> wrote in message
news:464377e4$0$21928$157c6196@dreader1.cybercity.dk...

[8<8<8<]
> Jeg paster lige det hele ind her i bunden. Så kan du og andre kritisere
> det sønder og sammen hvis I vil være så venlige.

Ok - så vil jeg prøve at være venlig

> Templateløsningen, as it, den er ikke værd at vise frem. Som sagt.. kritik
> er meget velkommen, for lige nu er c++ er ikke lige mit sprog.
>
> --------------------------------------------------------
>
> #pragma once
>
> #include <tchar.h>
> #include <vector>
>
> //the current types of geometry
> enum GeometryType{UNDEFINED=0,BOX,SPHERE,MODEL3D};

Det er designmæssigt uheldigt at have eet sted hvor alle afledte typer er
nævnt.
Det betyder nemlig at alle klasser bliver afhængige af alle klasser. Hvis
man tilføjer een ny klasse skal _alle_ klasser oversættes, fordi de reelt er
blevet ændret uden man har gjort sig det klart.
Den form for design skalerer ikke til store projekter.

Giver det nogen mening af have UNDEFINED ?


>
> //just a placeholder for a transformation matrix
> struct Matrix4x4
> {
> int somethingWeWontNeedHere;
> };
>
>
> //abstract base class, which forces children to implement the CollidedWith
> method
> class Geometry
> {
> public:
> virtual bool CollidesWith(Geometry* other, Matrix4x4 t, Matrix4x4 r,
> Matrix4x4 s)=0;

Som jeg skrev tidligere: brug reference i stedet for pointer.

> GeometryType GetType(){return geometryType;}
> protected:
> GeometryType geometryType;

Den kan erklæres const, og _skal_ være private.
Lav en protected constructor, som kræver at den afledte klasse giver typen:
Geometry(GeometryType geoType) :
geometryType(geoType) {}

Det sikrer:
* Datamedlemmer er private - det er normal vigtigt
* Intentionen i brugen gøres eksplicit:
* Det er den afledte klasses ansvar at specificeret typen
* Typen er konstant
* Man bruger initialisering frem for tildeling

> };
>

[8<8<8<]
> //a box geometry which knows about only boxes
> class Box: public Geometry
> {
> public:
> Box(){geometryType=BOX;}

Foretræk initialisering frem for tildeling, ved at lave den nævnte
constructor i Geometry:
Box :
Geometry(BOX)
{}

>
> virtual bool CollidesWith(Geometry* other, Matrix4x4 t, Matrix4x4 r,
> Matrix4x4 s)
> {
> switch(other->GetType())
> {
> case BOX:
> printf("BOX vs. BOX, resolved by BOX\n");

Foretræk at bruge C++ streams frem for C printf-familien - det er
typesikkert.

> return true;
> break;
> default:
> printf("BOX vs something new and unknown, resolved by BOX\n");
> return false;//we dont know how to collide, so we don't

Hvis du når hertil er der tale om en programmeringsfejl - brug assert:
assert(!"BOX vs something new and unknown, resolved by BOX");

På den måde slipper du måske at returnere bool.
Alternativt så slipper du for at returnere en lovlig værdi som en fejlkode:
true: kollision
false: ingen kollision eller programmerings fejl!!!

> break;
> }
> }
> };
>
> //a sphere geometry which knows about boxes and spheres
> class Sphere: public Geometry
> {
> public:
> Sphere(){geometryType=SPHERE;}
>
> virtual bool CollidesWith(Geometry* other, Matrix4x4 t, Matrix4x4 r,
> Matrix4x4 s)
> {
> switch(other->GetType())
> {
> case BOX:
> //transform geometry by matrices and do actual testing
> printf("SPHERE vs. BOX, resolved by SPHERE\n");
> return true;
> break;
> case SPHERE:
> printf("SPHERE vs. SPHERE, resolved by SPHERE\n");
> return true;
> break;
> default:
> printf("SPHERE vs something new and unknown, resolved by SPHERE\n");
> return false; //we dont know how to collide, so we don't
> break;
> }

Det du reelt har lavet er en halv Visitor, og du har den uheldige kobling
som Visitor typisk giver: alle klasser afhænger af alle klasser, og hvis der
tilføjes en ny skal alle klasser rettets.
Men du kunne lige så godt lave en reel Visitor, hvis du er på vej i den
retning (ikke oversat kode)

class Geometry
{
public:
virtual bool CollidesWith(Geometry const& other) const = 0;

protected:
virtual bool CollidesWith(Box const& box) const = 0;
virtual bool CollidesWith(Sphere const& sphere) const = 0;
};

class Box : public Geometry
{
public:
virtual bool CollidesWith(Geometry const& other) const
{ other.CollidesWith(*this); }

private:
virtual bool CollidesWith(Box const& box) const
{ cout << "Box<->Box collision" << endl; }
virtual bool CollidesWith(Sphere cosnt& sphere) const
{ cout << "Box<->Sphere collision" << endl; }
};

[8<8<8<]
> scene.AddGeometry(new SceneElement(&house)); //second element using the
> house model

Hvem frigiver hukommelsen.
Det er ofte en god idee at have en klar definition af hvem der har
ejerskabet.

--
Venlig hilsen

Mogens Hansen



Janus (12-05-2007)
Kommentar
Fra : Janus


Dato : 12-05-07 13:31

>> enum GeometryType{UNDEFINED=0,BOX,SPHERE,MODEL3D};
>
> Det er designmæssigt uheldigt at have eet sted hvor alle afledte typer er
> nævnt.
> Det betyder nemlig at alle klasser bliver afhængige af alle klasser. Hvis
> man tilføjer een ny klasse skal _alle_ klasser oversættes, fordi de reelt
> er blevet ændret uden man har gjort sig det klart.
> Den form for design skalerer ikke til store projekter.

Det kan jeg godt se, nu du siger det. Projektet er godt nok lille bitte, men
jeg havde nok lavet sammen fejl med et stort. Hvad er så et godt alternativ?
At man dropper enum og bare giver hver klasse en int?

> Giver det nogen mening af have UNDEFINED ?

På et tidspunkt ville jeg gerne kunne fange om klassen blev initialiseret
ordentligt. Egentlig giver det ikke mening længere, for sådan en fejl
behandles ikke.


>> virtual bool CollidesWith(Geometry* other....
> Som jeg skrev tidligere: brug reference i stedet for pointer.

Det tror jeg ikke jeg har læst. Hvad er grunden, andet end sikkerhed mod at
man misbruger pointeren? Er en reference og en pointer ikke underliggende
det samme, men med en anden syntax som gør at man ikke kan pege en reference
andre steder hen?

>> GeometryType GetType(){return geometryType;}
>> protected:
>> GeometryType geometryType;
>
> Den kan erklæres const, og _skal_ være private.
> Lav en protected constructor, som kræver at den afledte klasse giver
> typen:
> Geometry(GeometryType geoType) :
> geometryType(geoType) {}

Ok. Ja, jeg tænkte at da child skulle sætte den, så måtte den væe protected.
Jeg synes ikke jeg i c++ har så meget held med at kræve at base
constructoren bliver kaldt, så det droppede jeg... men nu jeg tænker over
det, så må det have været en parameterløsconstructor, for ellers SKAL den da
kaldes fra childs. Dum fejl. Jeg retter som du foreslår. Opgaven er godt nok
afleveret, men klogere kan jeg vel stadig blive.


> Foretræk initialisering frem for tildeling, ved at lave den nævnte
> constructor i Geometry:

Hvad er den praktiske forskel egentlig? Med initialisering bliver værdien
sat når klassen allokeres, hvor den med tildeling først bliver sat af
constructoren. Det vil sige at hvis det eksempelvis er en int, så vil den
først blive allokeret i klassen (on new) og initialiseret til ints default
værdi af nul, og derefter kaldes constructor som initialiserer igen, til
anden værdi?

> Foretræk at bruge C++ streams frem for C printf-familien - det er
> typesikkert.

Det bør jeg. Lærte c. Nogle år senere c++, men det blev til c med klasser.
Nok derfor det var så godt da jeg skiftede til c#. Der var der ingen legacy
at slæbemed. Bør egentlig lære c plus plus ordentligt en dag.

>
>> return true;
>> break;
>> default:
>> printf("BOX vs something new and unknown, resolved by BOX\n");
>> return false;//we dont know how to collide, so we don't
>
> Hvis du når hertil er der tale om en programmeringsfejl - brug assert:
> assert(!"BOX vs something new and unknown, resolved by BOX");

Egentlig ikke. Det kan tænkes at et megetKompliceretGeometry-objekt ikke
implementerer kollision med say en sphere, selvom sphere er ældst. Nu ved
jeg ikke helt hvor meget der fremgår af løsningen, men tanken er at når man
tilføjer nye typer geometri (nye klasser hen ad vejen) til sit system, så
bør de kunne håndtere kollision mellem sig selv og allerede eksisterende
geometryklasser. Det er dog ikke et _krav_, så en ny type kan springe nogle
ælde typer over. Det resulterer i at de så bare ikke _kan_ kollidere med
hinanden. Selve kollisionsdetektionen skal kodes der hvor der lige nu er en
printf. Det er bare så man kan se at denne her klasse prøver at beregne
kollision med denne her anden klasse. derfor skal der returneres en bool.
Den angiver om de faktisk kolliderede. Reelt skal der også returneres mere
info, men det er bare et testframework der handler om noget andet. Nå, det
var vist et sidespor


> Det du reelt har lavet er en halv Visitor, og du har den uheldige kobling
> som Visitor typisk giver: alle klasser afhænger af alle klasser, og hvis
> der tilføjes en ny skal alle klasser rettets.

Nej, det er netop det fine.
Say der findes en type kaldet Box.
Box kan kollidere med box.
Senere tilføjer man så sphere.
Boxes og spheres liger i een container. Man løber containeren igennem for
alle par (unikke par, så AB er det samme som BA og kun AB findes) og for
hvert par siger man
nyesteKlasse.KolliderMed(ældsteKlasse). Det betyder at BoxSphere får sphere
til at kigge på box og lave kollision. Box kender ikke spehere, men sphere
kender box, da sphere blev skrevet mens box allerede var skrevet. Nyeste bør
kender ældste. Ved nye klasser, så skal kun den nyeste skrives i.
Det er det jeg bruger den enum til atstyre. Den nyeste klasse vil have den
enum der har den største værdi (en slags generationsnummer). Det er det der
er lidt fikst, synes jeg.

Den visitor du skriver herunder, den kræver at du ændrer i geometry, hvor du
laver en ny CollidesWith, og i den nye klasse.
Det kræver jo også at de ældre klasser skal kende de nyere, da der er en
abstract metode i geometry, hvad alle klaserne er, som skal implementeres.
Så hvis du ha Box og senere tilføjer sphere, så skal du rete i geometry,
implementere den nye metode i alle geometry-objekter og skrive din sphere.
Er det ikke korrekt forstået?

> Hvem frigiver hukommelsen.
> Det er ofte en god idee at have en klar definition af hvem der har
> ejerskabet.

Ups Det er der ikke nogen der gør.
Det vil sige... det gør windows når programmet slutter. For en ordens skyld,
så burde det nok lige tages med, ja.

Takker for kritikken. Jeg håber da du er frisk på at uddybe de steder hvor
jeg måske ikke var helt med. Håber ikke mit svar har fået en for defensiv
tone, for det er faktisk rart med lidt indsigtsfuld kritik. Jeg tror bare
ikke helt du fangede ideen med generationer for geometryklasserne.



Mogens Hansen (12-05-2007)
Kommentar
Fra : Mogens Hansen


Dato : 12-05-07 15:32


"Janus" <jan@no.mail.no> wrote in message
news:4645b3a6$0$21925$157c6196@dreader1.cybercity.dk...

[8<8<8<]
> Det kan jeg godt se, nu du siger det. Projektet er godt nok lille bitte,

Det spiller ingen rolle

> men jeg havde nok lavet sammen fejl med et stort.

Nemlig

> Hvad er så et godt alternativ? At man dropper enum og bare giver hver
> klasse en int?

Nej.
At man lader compileren lave RTTI

Det kommer om lidt

>
>> Giver det nogen mening af have UNDEFINED ?
>
> På et tidspunkt ville jeg gerne kunne fange om klassen blev initialiseret
> ordentligt. Egentlig giver det ikke mening længere, for sådan en fejl
> behandles ikke.

Netop - det giver ingen mening: slet det ellers tror dem der skal læse koden
senere at der er tænkt over det

>
>
>>> virtual bool CollidesWith(Geometry* other....
>> Som jeg skrev tidligere: brug reference i stedet for pointer.
>
> Det tror jeg ikke jeg har læst. Hvad er grunden, andet end sikkerhed mod
> at man misbruger pointeren? Er en reference og en pointer ikke
> underliggende det samme,

Jo - normalt.

> men med en anden syntax som gør at man ikke kan pege en reference andre
> steder hen?

En pointer kan have værdien nul - den signalerer at den kan pege på
ingenting, og det giver ingen mening.
En reference skal altid referere til et gyldigt objekt.

Dertil kommer at en pointer kan sættes til at pege til et andet objekt, men
en reference kan ikke sættes til at referere til noget andet, og _skal_
derfor initialiseres.


[8<8<8<]
> Ok. Ja, jeg tænkte at da child skulle sætte den, så måtte den væe
> protected. Jeg synes ikke jeg i c++ har så meget held med at kræve at base
> constructoren bliver kaldt,

Hvad mener du ?

> så det droppede jeg... men nu jeg tænker over det, så må det have været en
> parameterløsconstructor, for ellers SKAL den da kaldes fra childs. Dum
> fejl. Jeg retter som du foreslår. Opgaven er godt nok afleveret, men
> klogere kan jeg vel stadig blive.

Det er fint - så risikerer jeg ikke at du bare skriver af

>
>
>> Foretræk initialisering frem for tildeling, ved at lave den nævnte
>> constructor i Geometry:
>
> Hvad er den praktiske forskel egentlig?

Den praktiske forskel er f.eks. at data-medlemmet geometryType kan være
"const" - hvilket er væsentligt.
Det betyder også generelt at man ikke risikerer uinitialiserede variable.
Det betyder at man kan bruge typer, der _skal_ initialiseres som f.eks.
referencer.

> Med initialisering bliver værdien sat når klassen allokeres,

Nej - den bliver sat når variablen bliver initialiseret.
I C++ er det vigtigt at skelne mellem:
* allokering af hukommelse (som i "operator new"). objektet eksisterer
ikke endnu
* initialisering (som i constructor). objektet går fra ikke at eksistere
til at eksistere
* tildeling (som i "operator="). objektet eksisterer

> hvor den med tildeling først bliver sat af constructoren.

Og altså efter objektet eksisterer.

> Det vil sige at hvis det eksempelvis er en int, så vil den først blive
> allokeret i klassen (on new) og initialiseret til ints default værdi af
> nul,

Som default initialiseres en int med ingenting - altså ubestemt værdi.
Den initialiseres med 0, hvis man bruger default-constructor syntaks:
int i = int();
eller i en klasses initialiser-liste:
myclass::myclass() :
int_member(int())
{
}
hvilket er væsentlig i templates, hvor der ikke står "int" men T.

> og derefter kaldes constructor som initialiserer igen, til anden værdi?

Nej - et objekt bliver kun initialiseret een gang.


[8<8<8<]
> Det er dog ikke et _krav_, så en ny type kan springe nogle ælde typer
> over. Det resulterer i at de så bare ikke _kan_ kollidere med hinanden.

Holder den ?
Jeg foretrækker at koden er eksplicit.

[8<8<8<]
> Nej, det er netop det fine.
> Say der findes en type kaldet Box.
> Box kan kollidere med box.
> Senere tilføjer man så sphere.
> Boxes og spheres liger i een container. Man løber containeren igennem for
> alle par (unikke par, så AB er det samme som BA og kun AB findes) og for
> hvert par siger man
> nyesteKlasse.KolliderMed(ældsteKlasse). Det betyder at BoxSphere får
> sphere til at kigge på box og lave kollision. Box kender ikke spehere, men
> sphere kender box, da sphere blev skrevet mens box allerede var skrevet.
> Nyeste bør kender ældste. Ved nye klasser, så skal kun den nyeste skrives
> i.
> Det er det jeg bruger den enum til atstyre. Den nyeste klasse vil have den
> enum der har den største værdi (en slags generationsnummer).

Den er jeg med på.
Du har reelt koblingsmæssigt lavet det moralske ækvivalent til Visitor:
* Tilføj en klasse og verden skal genoversættes
* Fjern en klasse og verden skal genoversættes

Der er ingen måde du kan snakke dig fra det

> Det er det der er lidt fikst, synes jeg.

Det syntes jeg ikke
Se længere nede.

>
> Den visitor du skriver herunder, den kræver at du ændrer i geometry, hvor
> du laver en ny CollidesWith, og i den nye klasse.

Jeps - det er en fundamental svaghed ved Visitor
Jeg er ikke nogen stor fan af Visitor.

> Det kræver jo også at de ældre klasser skal kende de nyere, da der er en
> abstract metode i geometry, hvad alle klaserne er, som skal implementeres.

Hvorfor skelne mellem nyere og ældre.
Bør alle klasser ikke være ligestillede ?
Hvad hvis man er en stor gruppe udviklere og 2 laver en nye geometry type
samtidig ?
Den holder ikke!

Se mit bud som kommer i et senere indlæg.

> Så hvis du ha Box og senere tilføjer sphere, så skal du rete i geometry,
> implementere den nye metode i alle geometry-objekter og skrive din sphere.
> Er det ikke korrekt forstået?

Både og.
Man kan i Geometry klassen lave en default implementering, som vender
parametrene:
class Geometry
{
// ...

virtual void Collide(NewGeometry const& newGeo)
{ newGeo.Collide(*this); }
//...
}


[8<8<8<]
> Takker for kritikken. Jeg håber da du er frisk på at uddybe de steder hvor
> jeg måske ikke var helt med. Håber ikke mit svar har fået en for defensiv
> tone, for det er faktisk rart med lidt indsigtsfuld kritik. Jeg tror bare
> ikke helt du fangede ideen med generationer for geometryklasserne.

Jo - den fangede jeg godt.
Men du _har_ afhængigheden på tværs af klasse-hierakiet.
Prøv at tænk over hvad der sker hvis een af de gamle typer klasser skal
slettes.

Det er koblingsmæssigt langt bedre at håndtere kollision-beregning i en
global funktion.
Spørgsmålet om symetri (AB==BA) ville jeg løse på en anden måde, nemlig med
en template der vender parametrene

template <typename T1, typename T2>
inline void collide(T1 const& t_1, T2 const& t_2)
{
collide(t_2, t_1);
}


--
Venlig hilsen

Mogens Hansen



Janus (12-05-2007)
Kommentar
Fra : Janus


Dato : 12-05-07 16:15

> En pointer kan have værdien nul - den signalerer at den kan pege på
> ingenting, og det giver ingen mening.
> En reference skal altid referere til et gyldigt objekt.

Det er egentlig sjovt nok, for i princippet kan man jo godt pege på adresse
0. At man så ikke gør det i programmer til et os, det er jo en anden sag.
Ja, måske ordkløver jeg, men alligevel

>> Opgaven er godt nok afleveret, men klogere kan jeg vel stadig blive.
>
> Det er fint - så risikerer jeg ikke at du bare skriver af

Bare rolig. Jeg kopierer aldrig.. jeg bliver inspireret

>> Med initialisering bliver værdien sat når klassen allokeres,
>
> Nej - den bliver sat når variablen bliver initialiseret.

Det ser ud til at du har ret.
I c# bliver membervariabler initialiseret til defaultværdi. Det er så fordi
say en int er et objekt med default constructor der sætter den til 0.


>> Det er dog ikke et _krav_, så en ny type kan springe nogle ælde typer
>> over. Det resulterer i at de så bare ikke _kan_ kollidere med hinanden.
>
> Holder den ?
> Jeg foretrækker at koden er eksplicit.

Ja, det er ok. Man kan forestille sig situationer hvor visse typer kollision
ike er understøttet. Det var også et krav i opgaven at man skulle håndtere
mangler pænt, og jeg synes "pænt" er at man siger at hvis der ikke er en
handler for kollision, så kan der ikke ske kollision.

> Den er jeg med på.
> Du har reelt koblingsmæssigt lavet det moralske ækvivalent til Visitor:
> * Tilføj en klasse og verden skal genoversættes
> * Fjern en klasse og verden skal genoversættes
>
> Der er ingen måde du kan snakke dig fra det

True. Jeg har dog lavet det så man kan tilføje en klasse og veden skal ikke
genKODES. Jeg synes egentlig ikke det er så stort et problem at
genoversætte, men det er måske fordi jeg primært har kodet pascal og c#,
hvor det ikke tager så pokkers lang tid. Jeg synes det er vigtigere at man
aldrig skal rette i eksisterende kode når der tilføjes nyt.

>> Den visitor du skriver herunder, den kræver at du ændrer i geometry, hvor
>> du laver en ny CollidesWith, og i den nye klasse.
>
> Jeps - det er en fundamental svaghed ved Visitor
> Jeg er ikke nogen stor fan af Visitor.

Jamen det er ikke en svaghed ved "min" visitor.

>> Det kræver jo også at de ældre klasser skal kende de nyere, da der er en
>> abstract metode i geometry, hvad alle klaserne er, som skal
>> implementeres.
>
> Hvorfor skelne mellem nyere og ældre.
> Bør alle klasser ikke være ligestillede ?
> Hvad hvis man er en stor gruppe udviklere og 2 laver en nye geometry type
> samtidig ?
> Den holder ikke!

Jeg kan godt se hvor du vil hen, men grunden til at de ikke er ligestillede,
det er at jeg gerne ville lade beregningerne foregå i klasserne selv og ikke
i en eller anden alvidende superklasse. Da ældre ikke kan tage hensyn til
klasser som ikke fandtes da de blev skrevede, så må de nye tage ansvaret på
sig.
Hvis flere samtidigt koder, så skal de alligevel tale sammen om hvordan de
ser visse properties ved andres klasser. en firkant og en kugle er
fundamentalt forskelligt repræsenterede. Med de enums der var, så kan man
endda have en slags uddeling af typenummer.

> Se mit bud som kommer i et senere indlæg.

Det ser jeg frem til. Jeg siger jo ikke at der kan være bedre løsninger,
bare at jeg ikke helt ser problemerne ved den jeg skitserede. Særligt ikke
da det er ny klasse->omkodning kontra ny klasse->rekompilering.

> Man kan i Geometry klassen lave en default implementering, som vender
> parametrene:
> class Geometry
> {
> // ...
>
> virtual void Collide(NewGeometry const& newGeo)
> { newGeo.Collide(*this); }
> //...
> }

Ja, men du skal stadig i box skrive noget om sphere. En sphere som ikke
kendes mens du skriver box, så du skal tilbage og rette, ikke?

> Men du _har_ afhængigheden på tværs af klasse-hierakiet.
> Prøv at tænk over hvad der sker hvis een af de gamle typer klasser skal
> slettes.

Say vi skriver box, sphere, pyramid og senere sleter sphere
så vil der aldrig være et par objekter i verden hvor sphere er med, for den
findes jo ikke. box-box virker stadig da box klarer det. box-pyramid virker
også, da pyramid klarer den. pyramid-pyramix klarer pyramide også. Eneste
dårlige ting er at pyramiden vil have kode for sphere som aldrig bliver
brugt...? Selv det at sphere slettes fra den enum, det gør intet, da deres
int-værdi nok ændres, men i koden refereres til ved navn.




Mogens Hansen (12-05-2007)
Kommentar
Fra : Mogens Hansen


Dato : 12-05-07 17:11


"Janus" <jan@no.mail.no> wrote in message
news:4645d9ea$0$21925$157c6196@dreader1.cybercity.dk...

[8<8<8<]
> Det er egentlig sjovt nok, for i princippet kan man jo godt pege på
> adresse 0. At man så ikke gør det i programmer til et os, det er jo en
> anden sag. Ja, måske ordkløver jeg, men alligevel

Der er nærliggende - men hvis du initialiserer en reference med en dereferet
0 pointer eller en pointer til et nedlagt objekt, så har du undefined
behaviour.

Man kan _altid_ gå ud fra at en reference refererer til et lovligt objekt -
ellers er der nogen som ikke har overholdt deres forpligtigelser.

[8<8<8<]
> Bare rolig. Jeg kopierer aldrig.. jeg bliver inspireret

Netop - det er meningen.

[8<8<8<]
>> Holder den ?
>> Jeg foretrækker at koden er eksplicit.
>
> Ja, det er ok. Man kan forestille sig situationer hvor visse typer
> kollision ike er understøttet. Det var også et krav i opgaven at man
> skulle håndtere mangler pænt, og jeg synes "pænt" er at man siger at hvis
> der ikke er en handler for kollision, så kan der ikke ske kollision.

Fint nok.
Se min løsning med brug af overloading

[8<8<8<]
> True. Jeg har dog lavet det så man kan tilføje en klasse og veden skal
> ikke genKODES. Jeg synes egentlig ikke det er så stort et problem at
> genoversætte, men det er måske fordi jeg primært har kodet pascal og c#,
> hvor det ikke tager så pokkers lang tid.

Det er væsentligt i store projekter.
Både på grund af oversættelsestider og binær kompatibilitet.

> Jeg synes det er vigtigere at man aldrig skal rette i eksisterende kode
> når der tilføjes nyt.

Det er rigtigt.

>
>>> Den visitor du skriver herunder, den kræver at du ændrer i geometry,
>>> hvor du laver en ny CollidesWith, og i den nye klasse.
>>
>> Jeps - det er en fundamental svaghed ved Visitor
>> Jeg er ikke nogen stor fan af Visitor.
>
> Jamen det er ikke en svaghed ved "min" visitor.

Du _har_ cirkulære afhængigheder i dit design - og det er altid meget
uheldigt:
Dine konkrete klasser afhænger af basisklassen - det er fundamentalt i
klassehierakier.
Din basisklasse afhænger af _alle_ afledte klasser ved at have den enum som
nævner alle klasser.

Det er svagheden i den klassiske Visitor.

[8<8<8<]
> Jeg kan godt se hvor du vil hen, men grunden til at de ikke er
> ligestillede, det er at jeg gerne ville lade beregningerne foregå i
> klasserne selv og ikke i en eller anden alvidende superklasse. Da ældre
> ikke kan tage hensyn til klasser som ikke fandtes da de blev skrevede, så
> må de nye tage ansvaret på sig.

Det forstår jeg godt - men det er ikke optimalt.

> Hvis flere samtidigt koder, så skal de alligevel tale sammen om hvordan de
> ser visse properties ved andres klasser. en firkant og en kugle er
> fundamentalt forskelligt repræsenterede. Med de enums der var, så kan man
> endda have en slags uddeling af typenummer.

Nej den holder ikke.
Een koder firkant, en anden kugle og en tredie hvad der sker når de
kolliderer.

Hvis designet skal skalere, _skal_ typerne være ligestillet og uafhængige.
Jeg kan anbefale bogen
Large -Scale C++ Software Design
John Lakos
ISBN 0-201-63362-0
som er god, selvom den er lidt gammel (det ses på visse dele af C++ sproget)

Man kan selvfølgelig indvende at den konkrete opgave ikke er så stor.
Generelt er små problemer ikke interessant - en hvilken som helst løsning
kan løse problemet. Det er interessant at prøve at løse store problemer

>
>> Se mit bud som kommer i et senere indlæg.
>
> Det ser jeg frem til. Jeg siger jo ikke at der kan være bedre løsninger,
> bare at jeg ikke helt ser problemerne ved den jeg skitserede. Særligt ikke
> da det er ny klasse->omkodning kontra ny klasse->rekompilering.

Du _har_ uheldig kobling i dit design.

[8<8<8<]
> Say vi skriver box, sphere, pyramid og senere sleter sphere
> så vil der aldrig være et par objekter i verden hvor sphere er med, for
> den findes jo ikke.

Nej - men du skal slette SPHERE fra din enum, og hvad sker der så ?

> box-box virker stadig da box klarer det. box-pyramid virker også, da
> pyramid klarer den. pyramid-pyramix klarer pyramide også. Eneste dårlige
> ting er at pyramiden vil have kode for sphere som aldrig bliver brugt...?
> Selv det at sphere slettes fra den enum, det gør intet, da deres int-værdi
> nok ændres, men i koden refereres til ved navn.

Det er vist ikke rigtigt.
Navnet SPHERE nævnes i dine switch statements, og hvis den værdi ikke
længere findes skal der ryddes op.

--
Venlig hilsen

Mogens Hansen



Janus (12-05-2007)
Kommentar
Fra : Janus


Dato : 12-05-07 17:57

> Du _har_ cirkulære afhængigheder i dit design - og det er altid meget
> uheldigt:
> Dine konkrete klasser afhænger af basisklassen - det er fundamentalt i
> klassehierakier.
> Din basisklasse afhænger af _alle_ afledte klasser ved at have den enum
> som nævner alle klasser.

Jeg kan ikke se hvordan det er at base afhænger af de afledte, at der er
samme type i dem. Den enum er jo bare en smart int, og du kan da ikke sige
at fordi base og afledte begge har en int i sig så er de afhængige af
hinanden. Jeg synes ikke helt jeg ser den cirkulære afhængighed. Slet en
klasse, eller skriv en ny, og kun den nye skal skrives.


> Nej den holder ikke.
> Een koder firkant, en anden kugle og en tredie hvad der sker når de
> kolliderer.

Jah. Tanken e jo at en koder firkant og en koder hvordan de kolliderer. En
uge efter synes man at man vil have kugler med også.

> Hvis designet skal skalere, _skal_ typerne være ligestillet og uafhængige.

Jeg synes stadig ikke helt jeg ser hvorfor det er tilfældet. Hvad er der
galt i at en ny type skal kode de kollisioner som den vil være med i, og at
en gammel type af gode grunde ignorerer nye typer, da de ikke findes mens
den skrives?


> Jeg kan anbefale bogen
> Large -Scale C++ Software Design
> John Lakos
> ISBN 0-201-63362-0
> som er god, selvom den er lidt gammel (det ses på visse dele af C++
> sproget)

Vil se om jeg kan finde en evalueringsversion og tage et kig.

> Man kan selvfølgelig indvende at den konkrete opgave ikke er så stor.
> Generelt er små problemer ikke interessant - en hvilken som helst løsning
> kan løse problemet. Det er interessant at prøve at løse store problemer
>

Jah, små løsninger som ikke skal være hurtige, de er lidt ligegyldige. Her
er det så meningen atdet noetop skal skalere, men... say der laves tusind
typer geometry og du så vil tilføje nummer 1001... så er det jo rimeligt nok
at du skal skrive kollisionskode mod den nye og de gamle. Et sted skal det
jo skrives uanset, og det er ikke sket tidligere da din nye 1001 først
findes nu. Du skriver så koden i din klasse så du slet ikke skal ændre i
andet kode, og derefter adder du din source til projektet, rekompilerer kun
din nye klasse (i hvert fald hvis du bruger en int istedetfor en enum) og
viola. Systemet kender nu din klasse og ting flyver rundt ogkolliderer. Det
skalerer da fint??

> Nej - men du skal slette SPHERE fra din enum, og hvad sker der så ?
> Navnet SPHERE nævnes i dine switch statements, og hvis den værdi ikke
> længere findes skal der ryddes op.

True. Havde lige overset at det fandtes i switches som navn.Så laver jeg den
enum om til en int, så første objekttype er 0, anden er 1 etc.
med box,sphere.pyramid sletter jeg så sphere, som er 1, og den switch rammes
ikke mere. Nu skal der bare være en værdi et sted som altid holder styr på
sidste brugte id, så når man adder oddGeometry, så får den ikke spheres id
men 3 som er efter pyramid. Nu er der da absolut intet cirkulært over
det.... right?



Mogens Hansen (12-05-2007)
Kommentar
Fra : Mogens Hansen


Dato : 12-05-07 18:44


"Janus" <jan@no.mail.no> wrote in message
news:4645f1dd$0$7607$157c6196@dreader2.cybercity.dk...
>> Du _har_ cirkulære afhængigheder i dit design - og det er altid meget
>> uheldigt:
>> Dine konkrete klasser afhænger af basisklassen - det er fundamentalt i
>> klassehierakier.
>> Din basisklasse afhænger af _alle_ afledte klasser ved at have den enum
>> som nævner alle klasser.
>
> Jeg kan ikke se hvordan det er at base afhænger af de afledte, at der er
> samme type i dem. Den enum er jo bare en smart int, og du kan da ikke sige
> at fordi base og afledte begge har en int i sig så er de afhængige af
> hinanden. Jeg synes ikke helt jeg ser den cirkulære afhængighed.

Jeg vil hævde at du har en cirkulær afhængighed, via enumen.
Dit design ligger meget tæt op af Visitor, selvom implementeringen er lidt
anderledes end den klassiske Visitor.

> Slet en klasse, eller skriv en ny, og kun den nye skal skrives.

Du tager fejl.
Prøv at skriv koden.

I virkeligheden vil du caste fra bais-klassen til de 2 konkrete klasser, og
_der_ vil du nævne navnet på klassen.
Gæt hvad der sker når du sletter den ene klasse fra systemet.

[8<8<8<]
>> Hvis designet skal skalere, _skal_ typerne være ligestillet og
>> uafhængige.
>
> Jeg synes stadig ikke helt jeg ser hvorfor det er tilfældet. Hvad er der
> galt i at en ny type skal kode de kollisioner som den vil være med i, og
> at en gammel type af gode grunde ignorerer nye typer, da de ikke findes
> mens den skrives?

Man skal minimere afhængighederne mellem klasserne og lade være med at give
dem for mange forskellige ansvar.
Det er gode gamle coupling and cohesion.

[8<8<8<]
> Vil se om jeg kan finde en evalueringsversion og tage et kig.

Nu er der blevet nævnt nogle gode bøger i disse 2 tråde, så hvis du kigger
på min hjemmeside vil du kunne se billeder som jeg har taget fornyligt af
forfatterne
Daveed Vandevoorde:
http://www.hansen4.dk/fotoalbum/displayimage.php?album=54&pos=3
Nicolai Josuttis:
http://www.hansen4.dk/fotoalbum/displayimage.php?album=54&pos=14 i midten
kigger lidt op
James Coplien:
http://www.hansen4.dk/fotoalbum/displayimage.php?album=54&pos=5 med den
hvide T-shirt
John Lakos:
http://www.hansen4.dk/fotoalbum/displayimage.php?album=54&pos=5 med ryggen
til helt til højre
Andrei Alexandrescu:
http://www.hansen4.dk/fotoalbum/displayimage.php?album=54&pos=10

[8<8<8<]
> Et sted skal det jo skrives uanset, og det er ikke sket tidligere da din
> nye 1001 først findes nu.

Jeps.
Naturligvis skal der rettes.
Det væsentlige at man tilstræber at begrænse ændringerne, så det kun er de
relevante steder der rettes.

Prøv at kig mine eksempler igennem og sammenlign.

> Du skriver så koden i din klasse så du slet ikke skal ændre i andet kode,
> og derefter adder du din source til projektet, rekompilerer kun din nye
> klasse (i hvert fald hvis du bruger en int istedetfor en enum) og viola.
> Systemet kender nu din klasse og ting flyver rundt ogkolliderer. Det
> skalerer da fint??

Ja ja - det kunne nemt være være, men også bedre.

[8<8<8<]
> Så laver jeg den enum om til en int, så første objekttype er 0, anden er 1
> etc.
> med box,sphere.pyramid sletter jeg så sphere, som er 1, og den switch
> rammes ikke mere. Nu skal der bare være en værdi et sted som altid holder
> styr på sidste brugte id, så når man adder oddGeometry, så får den ikke
> spheres id men 3 som er efter pyramid. Nu er der da absolut intet
> cirkulært over det.... right?

Nej - det ændrer ikke noget fundamentalt.
Du har bare fjernet compilerens mulighed for at opdage det, og i stedet
overlade det til disiplin at sikre at klasserne har unikke ID.
Det er et dårligt bytte.

Når du fjerner typen der har værdien 1 skal all koden, som bruger den værdi
fjernes fra alle klasser med højere værdi.
Det er rimeligt indlysende at kollisions håndteringen af 2 konkrete typer at
man caster til de 2 konkrete typer, så deres navn er nævnt, og som sådan er
man (heldigvis) tvunget til at slette koden.

Man skal bruge et automatisk system til RTTI og håndteringen af kollision
skal ud af klasserne.
I C++ kan man automatisk lave en ordnet række af typer, ved at bruge
funktionen type_info::before, men man kender ikke rækkefølgen som
programmør - hvilket effektiv forhindrer at man baserer sig på det

--
Venlig hilsen

Mogens Hansen



Janus (12-05-2007)
Kommentar
Fra : Janus


Dato : 12-05-07 21:17

>> Slet en klasse, eller skriv en ny, og kun den nye skal skrives.
>
> Du tager fejl.
> Prøv at skriv koden.

Det har jeg altså gjort. Kun den nye skal skrives.

> I virkeligheden vil du caste fra bais-klassen til de 2 konkrete klasser,
> og _der_ vil du nævne navnet på klassen.
> Gæt hvad der sker når du sletter den ene klasse fra systemet.

Jeg caster da ikke. Jeg kalder baseklassens virtuelle metode.

for(unsigned int i0=0;i0<sceneElements.size();i0++)
{
SceneElement *a = sceneElements[i0];
for(unsigned int i1=i0+1;i1<sceneElements.size();i1++) //start counter
from previous i1+0
{
SceneElement *b = sceneElements[i1];
//check collisions by delegating to the gemetry and providing the
transformation matrices
if(a->Geometry->GetType()>b->Geometry->GetType())
a->Geometry->CollidesWith(b->Geometry, a->Position,
a->Rotation,a->Scaling);
else
b->Geometry->CollidesWith(a->Geometry, b->Position,
b->Rotation,b->Scaling);
}
}

Den kodeblok skal aldrig skrives om.

> Man skal minimere afhængighederne mellem klasserne og lade være med at
> give dem for mange forskellige ansvar.
> Det er gode gamle coupling and cohesion.

Jah. Altså. Et sted skal man kunne håndtere boxSphere kollision. Man kan
gøre det i et af geometryobjekterne, eller man kan gøre det i en anden
klasse. Hvorfor dog ikke putte ansvaret for hvad man koliderer med hos en
selv?

http://www.hansen4.dk/fotoalbum/displayimage.php?album=54&pos=1

LOL. Hvad sker der her? Er c++ blevet erklæret officielt verdenssprog nummer
et?

> Naturligvis skal der rettes.
> Det væsentlige at man tilstræber at begrænse ændringerne, så det kun er de
> relevante steder der rettes.
>
> Prøv at kig mine eksempler igennem og sammenlign.

Altså. I mit design skal der ikke rettes, forudsat at enum udskiftes med en
ægte int. Jeg har set dine eksempler igennem et par gange, og jeg synes
altså du skal rette i en del eksisterende kode for at tilføje nye klasser.

>> Nu er der da absolut intet cirkulært over det.... right?
>
> Nej - det ændrer ikke noget fundamentalt.

Nu bliver jeg altså snart stædig her. Hvordan dælen ser du at der skal
rettes i eksisterende kode, og at der er cirkulære afhængigheder i min
løsning? Jeg ved absolut sikkert at det at tilføje en ny klasse, det kræver
ingen ændringer, og at hvis enum udskiftes med en int, så sker der heler
ingen ændringer ved sletning af en klasse. Det sidste vil nu iøvrigt være en
underlig situation.

> Når du fjerner typen der har værdien 1 skal all koden, som bruger den
> værdi fjernes fra alle klasser med højere værdi.

Nej da. Der sker bare det at switch(id) case 3: aldrig længere kaldes, hvis
det er klassen med id=3 der er blevet zappet.
At slette klasser, det vil give kode i de senere klasser som aldrig kaldes,
men der skal intet ændres. Når man så samtidigt husker på at det vil være en
meget sjælden ting at klasser fjernes fra en sådan engine, så er selv
muligheden for ikke-kaldt kode intet problem.

Jeg tror altså ikke helt du forstår hvad det er jeg gør.. god c++ eller ej.
Skriv en ny klasse og kun dens kode skal pilles i. Slet en klasse og du
fjerner sådan set bare kildekoden fra projektet. Hvis du stadig ikke ser
hvad jeg mener, og du stadig er intereseret i diskussionen, så vil jeg gerne
prøve at uddybe i næste post.



Mogens Hansen (12-05-2007)
Kommentar
Fra : Mogens Hansen


Dato : 12-05-07 23:19


"Janus" <jan@no.mail.no> wrote in message
news:464620da$0$21924$157c6196@dreader1.cybercity.dk...

[8<8<8<]
> Den kodeblok skal aldrig skrives om.

Det er ikke den kodeblok jeg talte om, men fra dit oprindelige eksempel:

virtual bool CollidesWith(Geometry* other, Matrix4x4 t, Matrix4x4 r,
Matrix4x4 s)
{
switch(other->GetType())
{
case BOX:
//transform geometry by matrices and do actual testing
printf("SPHERE vs. BOX, resolved by SPHERE\n");
return true;
break;
case SPHERE:
printf("SPHERE vs. SPHERE, resolved by SPHERE\n");
return true;
break;
default:
printf("SPHERE vs something new and unknown, resolved by SPHERE\n");
return false; //we dont know how to collide, so we don't
break;
}

I virkelig kode vil der stå noget i retningen af:
switch(other->GetType()) {
case BOX: {
Box* box = static_cast<Box*>(other);
...
}
break;

case SPHERE: {
Sphere* sphere = static_cast<Sphere*>(other);
...
}
break;
}

og så kan Box ikke fjernes fra hierakiet uden i gennemsnit halvdelen af
klasserne skal ændres.
Hvis det er ældste klasse skal alle andre afledte rettes.
Hvis det er nyeste klasse skal ingen andre rettes.

Det er væsentligt at bemærke at halvdelen stammer fra antagelsen om at
AB=BA, så man kun behøver at håndtere halvdelen af kombinationerne.

>
>> Man skal minimere afhængighederne mellem klasserne og lade være med at
>> give dem for mange forskellige ansvar.
>> Det er gode gamle coupling and cohesion.
>
> Jah. Altså. Et sted skal man kunne håndtere boxSphere kollision. Man kan
> gøre det i et af geometryobjekterne, eller man kan gøre det i en anden
> klasse.

Eller i en global funktion.
Det er ikke ligegyldigt hvor man lægger det. Eller kunne man nøjes med een
funktion i een fil.
En væsentlig del af design går netop ud på hvordan man fordeler
funktionaliteten mellem enheder (klasser, funktioner, filer etc), og hvilke
konsekvenser det har.

> Hvorfor dog ikke putte ansvaret for hvad man koliderer med hos en selv?

Fordi det giver uheldig og unødvendig kobling mellem klasserne.

[8<8<8<]
> LOL. Hvad sker der her?

Sammenfaldet slog mig bare.

[8<8<8<]
> Jeg har set dine eksempler igennem et par gange, og jeg synes altså du
> skal rette i en del eksisterende kode for at tilføje nye klasser.

Hvor ?

Man skal:
* Erklære og implementere den nye klasse - naturligvis
* Tilføje den nye type til den type_list, der opremser alle typer
* Man skal skrive de handler funktioner, for alle de typer kollision man
vil håndtere - naturligvis

Hvis man sletter en klasse er det fuldstændigt det samme man skal gøre:
* Slette klassen - naturligvis
* Fjerne klassen fra den type_list, der opremser alle typer
* Slette de handler funktioner, der benytter den slettede type -
naturligvis

Der er ingen kobling mellem de afledte klasser, alle de afledte klasser er
"ligeværdige" og det er symmetrisk at tilføje og slette typer.

Man har simpelt mulighed for at håndtere AB=BA eller AB<>BA. Det er blot et
spørgsmål om hvilke handler funktioner der skrives.

[8<8<8<]
> Nu bliver jeg altså snart stædig her. Hvordan dælen ser du at der skal
> rettes i eksisterende kode, og at der er cirkulære afhængigheder i min
> løsning? Jeg ved absolut sikkert at det at tilføje en ny klasse, det
> kræver ingen ændringer, og at hvis enum udskiftes med en int, så sker der
> heler ingen ændringer ved sletning af en klasse.

Hvis du bruger en enum, hjælper compileren dig med at sikre at hver klasse
har en unik ID.
Hvis du skifter den ud med en int påtager du dig selv ansvarer for at hver
klasse har en unik ID.
Fundamentalt ændrer det ikke noget, selvom man skjuler det for compileren.

Hvis du ændrer fra enum til int kan man tilføje en ny klasse uden at andet
end den nye klasse skal skrives og oversættes, men når der slettes en klasse
skal halvdelen af de afledte klasser i gennemsnit redigeres og
genoversættes.

Det skalerer ikke så godt at kræve disciplin som computeren ikke kan hjælpe
med at håndhæve:
* Alle klasser skal have en unik ID
* De klasser med højeste ID har ansvaret for håndtering af klasser med
lavere ID
når det ikke er nødvendigt.

> Det sidste vil nu iøvrigt være en underlig situation.

Det er en udemærket måde at evaluere designet på.

>
>> Når du fjerner typen der har værdien 1 skal all koden, som bruger den
>> værdi fjernes fra alle klasser med højere værdi.
>
> Nej da. Der sker bare det at switch(id) case 3: aldrig længere kaldes,
> hvis det er klassen med id=3 der er blevet zappet.
> At slette klasser, det vil give kode i de senere klasser som aldrig
> kaldes, men der skal intet ændres. Når man så samtidigt husker på at det
> vil være en meget sjælden ting at klasser fjernes fra en sådan engine, så
> er selv muligheden for ikke-kaldt kode intet problem.

Du tager simpelthen fejl.
I virkelig kode (som ikke bare skriver tekst til standard output) vil man
når man har fundet ud af hvilken type der er tale om, caste referencen
(eller pointeren) til basis klassen til den afledte klasse, som jeg viste
ovenfor.
Det vil så blive mere tydeligt hvordan klasserne afhænger af hinanden.

>
> Jeg tror altså ikke helt du forstår hvad det er jeg gør.

Det er jeg ret sikker på at jeg gør.

>. god c++ eller ej. Skriv en ny klasse og kun dens kode skal pilles i.

Jeps - hvis du bruger int i stedet for enum, og dermed forhindrer compileren
i at hjælpe dig.

> Slet en klasse og du fjerner sådan set bare kildekoden fra projektet.

Du tager fejl.
Du ser det bare ikke fordi du i den konkrete kode ikke bruger den
typeinformation som du reelt har udledt.

> Hvis du stadig ikke ser hvad jeg mener, og du stadig er intereseret i
> diskussionen, så vil jeg gerne prøve at uddybe i næste post.

Ok.

--
Venlig hilsen

Mogens Hansen



Janus (13-05-2007)
Kommentar
Fra : Janus


Dato : 13-05-07 08:27

> I virkelig kode (som ikke bare skriver tekst til standard output) vil man
> når man har fundet ud af hvilken type der er tale om, caste referencen
> (eller pointeren) til basis klassen til den afledte klasse, som jeg viste
> ovenfor.

OK! Ja, nu har du en pointe. Det kunne du nok godt have understreget lidt
tidligere.
Hvis der er kode som caster, så er det korrekt nok, det du siger. Det er nu
stadig muligt at være kreativ og ikke caste, men det vil være omstændigt at
lave generelle metoder i Geometry som tager generelle argumenter for at
teste kollision. Hvis man kan, så kan man også lige så godt lave en super
generel detektor.

Jeg vil så vende tilbage til den oprindelige argumentation som er at man
udvider en engine med nye typer geometri - man fjerner dem ikke igen. Der
kan dog være tænkte situationer hvor det sker, og så vil castingen være et
problem, ja.

Jeg er med på at det er godt at koble klasserne løst, så de ikke kender
hinanden. Samtidigt er det godt at kun skrive/rette kode eet sted når der
laves nye klasser og det er godt at sprede specifik kollisionsdetektion ud
så man ikek får en super duper voldsom klasse der kender alt og alle i
detaljer. Jeg synes nu stadig der er mere vundet end tabt ved den
forholdsvis stærke kobling mod de ældre generationer, men nu er jeg i det
mindste med på hvad du mener problemet er.

>>Skriv en ny klasse og kun dens kode skal pilles i.
> Jeps - hvis du bruger int i stedet for enum, og dermed forhindrer
> compileren i at hjælpe dig.

Det er igen et valg. Styr selv generationsnummeret, og skriv kun i een ny
gang kildekode, eller brug en enum og ret i alle ældre klasser.

>> Hvis du stadig ikke ser hvad jeg mener, og du stadig er intereseret i
>> diskussionen, så vil jeg gerne prøve at uddybe i næste post.
>
> Ok.

Det er så ikke nødvendigt. Jeg kan godt følge dig nu.
Jeg synes bare du vælger at skulle rette flere steder for at kunne håndtere
en tænkt situation, hvor jeg prioriterer at skrive eet sted og så til
gengæld ikke kan fjerne klasser igen. Det var en svipser at jeg ikke lige
indså hvordan casting ville medføre at klasser ikke kan fjernes uden
redigering.



Mogens Hansen (13-05-2007)
Kommentar
Fra : Mogens Hansen


Dato : 13-05-07 10:42


"Janus" <jan@no.mail.no> wrote in message
news:4646bdb9$0$7610$157c6196@dreader2.cybercity.dk...

[8<8<8<]
> OK! Ja, nu har du en pointe. Det kunne du nok godt have understreget lidt
> tidligere.



> Hvis der er kode som caster, så er det korrekt nok, det du siger. Det er
> nu
> stadig muligt at være kreativ og ikke caste, men det vil være omstændigt
> at
> lave generelle metoder i Geometry som tager generelle argumenter for at
> teste kollision. Hvis man kan, så kan man også lige så godt lave en super
> generel detektor.
>
> Jeg vil så vende tilbage til den oprindelige argumentation som er at man
> udvider en engine med nye typer geometri -

Den skalerer ikke optimalt.
Man kan ikke lave unit test af den nye klasse, uden at skulle have hele
verden med.
I mit design kan man lave unit test først med den nye klasse alene, og
derefter ved at medtage de 2 klasser (den nye og een af de gamle) samt
kollisiondetekteringen for de 2. Alle klasser er ligeværdige.
Det er alt sammen en konsekvens af kobling i systemet. Kobling er en
væsentlige egenskaber for store software systemer.

Hvis man tilføjer en ny metode til den ældste klasse skal alle klasser
oversættes. I gennemsnit skal halvdelen af klasserne genoversættes, når der
ændres på een af klasserne.

> man fjerner dem ikke igen.

Det tror jeg ikke på, og som vist er det er en unødvendig begrænsning.

[8<8<8<]
> Jeg er med på at det er godt at koble klasserne løst, så de ikke kender
> hinanden. Samtidigt er det godt at kun skrive/rette kode eet sted når der
> laves nye klasser

Hvorfor er det godt ?
Forestil dig at klasserne indgår i et større system med grafisk brugerflade
og netværksdistribution. Ville du så lade de afledte klasser (som Box og
Sphere) indehoder kode til det ?
Tilstræb at een klasse eller funktion har een opgave og løser den godt
(cohesion).

> og det er godt at sprede specifik kollisionsdetektion ud så man ikek får
> en super duper voldsom klasse der kender alt og alle i detaljer.

Enig.
Min pointe er at kollisions detektion skal helt ud af klasserne og ligge et
andet sted. Helst i frie funktioner - det giver mindst kobling.

> Jeg synes nu stadig der er mere vundet end tabt ved den forholdsvis stærke
> kobling mod de ældre generationer,

Det er givetvis rigtigt - dem har jeg ikke set
Jeg siger ikke at dit design er fuldstændighed håbløst, men du skrev "Så kan
du og andre kritisere det sønder og sammen hvis I vil være så venlige. ".

> men nu er jeg i det mindste med på hvad du mener problemet er.

Godt.

>
>>>Skriv en ny klasse og kun dens kode skal pilles i.
>> Jeps - hvis du bruger int i stedet for enum, og dermed forhindrer
>> compileren i at hjælpe dig.
>
> Det er igen et valg. Styr selv generationsnummeret, og skriv kun i een ny
> gang kildekode, eller brug en enum og ret i alle ældre klasser.

Eller vælg et radikalt anderledes design.
Det første, og mest avancerede design jeg postede, var radikalt, men havde
lille kobling og optimal O(1) look-up performance karakteristik.
De senere var mere konventionel, havde samme kobling men med O(n) look-up
performance hvor n er antallet af klasser - ikke objekter.

[8<8<8<]
> Jeg synes bare du vælger at skulle rette flere steder for at kunne
> håndtere en tænkt situation,

Kan du ikke lige uddybbe det ?

Hvordan flere steder ?
Man skal
* skrive den nye geometry klasse - naturligvis
* skrive de nødvendige kollision detektorer - naturligvis
* vedligeholde listen af alle typer på det sted hvor man naturligt har
afhængigheden

Hvordan tænkt situation ?
Er det tænkt at typer skal kunne fjernes ? Den køber jeg ikke.
Hvad med produkt konfigurationer (Mobile Edition vs. Enterprise), unit test
og parallel udvikling i større grupper ?

Det er alt sammen et spørgsmål om skalerings evne - og som vi er enige om
små problemer er rimelig uinteressante.

Der er væsentlige, fundamentale forskelle, som er værd at tænke grundigt
over.
De bøger jeg har nævnt er _gode_ kilder i denne sammenhæng.

--
Venlig hilsen

Mogens Hansen



Janus (13-05-2007)
Kommentar
Fra : Janus


Dato : 13-05-07 15:18

> Man kan ikke lave unit test af den nye klasse, uden at skulle have hele
> verden med.

Ikke klassen alene, nej. Det vil kræve klassen og de klasser den skal
kolidere med. Det er vel som sådan også ok?

> Forestil dig at klasserne indgår i et større system med grafisk
> brugerflade og netværksdistribution. Ville du så lade de afledte klasser
> (som Box og Sphere) indehoder kode til det ?

Den kode der er specifik for sphere passer da fint ind der: Jeg har generelt
i grafiske applikationer brugt det at en klasse kan finde ud af at tegne sig
selv. Der er så et generelt interface til det "papir" den skal tegne på. Det
synes jeg egentlig er en god måde. Ting der er specifikke for sphere, det
ligger i sphere. Ellers skal der være en stor supertegner som kender til
sphere, box etc etc.
Jeg ved ikke helt hvordan klaserne skulle have specifikke evner indenfor
netværk, så nej til den.

> Tilstræb at een klasse eller funktion har een opgave og løser den godt
> (cohesion).

Betyder cohesion i denne sammenhæng også at man isolerer ting. I dette
tilfælde isolerer man det der har med sphere at gøre i sin egen klasse. Både
kollision, fysisk simulering og evt. tegning.

> Min pointe er at kollisions detektion skal helt ud af klasserne og ligge
> et andet sted. Helst i frie funktioner - det giver mindst kobling.

Så du ville lave en funktion der kan kollidere objekt ditten med objekt
datten, og en anden funktion der kan klare nogle andre objekter? Ja, det
kunne også være en løsning. Det kræver så bare at eksterne metoder skal have
adgang til det inderste af geometrierne for at kunne fungere ordentligt. Det
er da ikke cohesion, men at sprede tingene rundt over det hele? Det er nu
ikke så skidt endda, for så kan man nærmest have et subdir med kildekode der
kan lave de forskellige kollisioner, så jeg kan egentlig godt lide ideen.

> Jeg siger ikke at dit design er fuldstændighed håbløst, men du skrev "Så
> kan du og andre kritisere det sønder og sammen hvis I vil være så venlige.
> ".

Nårh, du er bestemt ikke for grov eller kritisk. Jeg skulle måske have
skrevet
"Så kan du og andre kritisere det sønder og sammen hvis I vil være så
venlige. , men forbered jer på at jeg ikke uden videre accepterer det I
skriver. Jeg skal overbevises"
Du har skrevet nogle udmærkede pointer nu... nu da vi forstår hinanden.

>> Jeg synes bare du vælger at skulle rette flere steder for at kunne
>> håndtere en tænkt situation,
>
> Kan du ikke lige uddybbe det ?

Hvis vi igen antager at man udvider systemet med klasser, og ikek fjerner
dem igen, hvilket er rimeligt nok at antage, så skal jeg kun skrive eet
sted, nemlig i den nye type geometri. Du skal rette flere steder, som du
også selv skriver.
Hvis man fjerner klasse, og man faktisk typecaster, så skal der godt nok
rettes.
Jeg kan dog godt se fidusen i at hive kollisionsdetektionen ud af klasserne
og have en dælens masse funktioner/klasser som kan håndtere hver een type
kollision. Det behøver ikke være rodet.




Mogens Hansen (13-05-2007)
Kommentar
Fra : Mogens Hansen


Dato : 13-05-07 15:57


"Janus" <jan@no.mail.no> wrote in message
news:46471e17$0$7603$157c6196@dreader2.cybercity.dk...
>> Man kan ikke lave unit test af den nye klasse, uden at skulle have hele
>> verden med.
>
> Ikke klassen alene, nej. Det vil kræve klassen og de klasser den skal
> kolidere med. Det er vel som sådan også ok?

Man kan ikke sige at det er forkert.
Men man kan klart sige at det er bedre, hvis klasserne kan testes i mindre
enheder og selvstændigt.

>
>> Forestil dig at klasserne indgår i et større system med grafisk
>> brugerflade og netværksdistribution. Ville du så lade de afledte klasser
>> (som Box og Sphere) indehoder kode til det ?
>
> Den kode der er specifik for sphere passer da fint ind der: Jeg har
> generelt
> i grafiske applikationer brugt det at en klasse kan finde ud af at tegne
> sig
> selv. Der er så et generelt interface til det "papir" den skal tegne på.
> Det
> synes jeg egentlig er en god måde.

Ja - det er et meget almindeligt sted at starte i objektorienteret
programmering. Men den holder ikke.

Det ender med at man har brug for en adskillelse mellem præsentation og
logik.
Man ser det i klasiske 3 lags modeller med database-business logic-user
interface, eller i den klassiske Model-View-Controller arkitektur.

> Ting der er specifikke for sphere, det
> ligger i sphere. Ellers skal der være en stor supertegner som kender til
> sphere, box etc etc.

Eller en eller flere mapninger for hver type.
Man kunne forestille sig een tegnings algoritme til et oversigts kort og en
anden til et 3D detail kort.

Eller hvis man tager en dato klasse.
Hvordan skal den tegne sig ?
Som en kalender kontrol, en edit kontrol. Hvad hvis den pludselig skal
bruges i en web applikation, på en anden platform eller et kommando prompt ?

> Jeg ved ikke helt hvordan klaserne skulle have specifikke evner indenfor
> netværk, så nej til den.

Hvor ligger forskellen ?
Klassen skal repræsenteres på en skærm via et givent interface og på et
netværk via en given protokol.

>
>> Tilstræb at een klasse eller funktion har een opgave og løser den godt
>> (cohesion).
>
> Betyder cohesion i denne sammenhæng også at man isolerer ting.

At man løser een sammenhængende opgave.

> I dette
> tilfælde isolerer man det der har med sphere at gøre i sin egen klasse.
> Både
> kollision, fysisk simulering og evt. tegning.
>
>> Min pointe er at kollisions detektion skal helt ud af klasserne og ligge
>> et andet sted. Helst i frie funktioner - det giver mindst kobling.
>
> Så du ville lave en funktion der kan kollidere objekt ditten med objekt
> datten, og en anden funktion der kan klare nogle andre objekter?

Jeps.
Og formodentlig putte dem i hver sin fil, såsom CollideBoxSpere.cpp

> Ja, det
> kunne også være en løsning. Det kræver så bare at eksterne metoder skal
> have
> adgang til det inderste af geometrierne for at kunne fungere ordentligt.

Geometrierne skal stille tilstrække information til rådighed, men det
betyder ikke nødvendigvis adgang til den interne repræsentation.
Box skal naturligvis kunne fortælle om højde, bredde og dybde.

Hvis man igen tager en dato klasse, så skal man naturligvis have adgang til
at læse dato, måned, år og eventuelt uge nummer og uge dag.
Men man skal ikke have adgang til den interne repræsentation, som f.eks. kan
være antal dage siden 1/1-1970 eller som 3 tal (dato, måned og år) eller
noget helt andet.
Man skal heller ikke have mulighed for at ændre dato alene, men derimod
kunne tildele den værdien af en anden _lovlig_ dato.

> Det
> er da ikke cohesion, men at sprede tingene rundt over det hele?

Jo - Box tager sig af at være Box og ikke andet.
Collide(Box, Box) tager sig af hvad der sker når 2 Box objekter støder
sammen og ikke andet.
Det er cohesion - sammenhængende.

Alternativt skal Box tage sig af at være Box, kollision med den halve
verden, tegning på en GUI ...

[8<8<8<]
> Hvis vi igen antager at man udvider systemet med klasser, og ikek fjerner
> dem igen, hvilket er rimeligt nok at antage,

Det bliver vi nok ikke enige om.

> så skal jeg kun skrive eet sted, nemlig i den nye type geometri. Du skal
> rette flere steder, som du også selv skriver.

Der skal skrives nogenlunde lige meget kode:
* Det at være en ny geometri
* Håndtering af hvad der skal når den nye geometri kolliderer med de gamle
typer

Den principielle forskel ligger i om koblingen ligger inde i klasserne
(f.eks. i form af en switch i mange klasser) eller uden for klasserne (i
form af een eksplicit typeliste).

--
Venlig hilsen

Mogens Hansen



Janus (13-05-2007)
Kommentar
Fra : Janus


Dato : 13-05-07 19:42

> Eller en eller flere mapninger for hver type.
> Man kunne forestille sig een tegnings algoritme til et oversigts kort og
> en anden til et 3D detail kort.

True nok. Der vill jeg så lave en helt almindelig visitor.
Det er jo også lidt problematisk at tale om hvad der er smart og ikke smart,
uden at have en konkret sigutation. Godt nok er vi enige om at der ikkee r
meget at skule designe for små systemer, som må køre langsomt, men på den
anden side må man også vurdere hvad der er af reele behov. Eksempelvis kan
det være ok at lade sphere tegne sig selv i det system man vil lave, eller
også er det ikke ok fordi den skal kunne tegne sig selv på alverdens absurde
måder. Jeg mener derfor det er lidt meningsløst at diskutere ethvert design
i et ekstremt stort perspektiv. Man må se hvad virkeligheden er.

At jeg ikke lige kommenterer resten af din post, det betyder ikke at jeg
ikke læste den. Jeg har bare ikke så meget nyt at sige, og vi er på mange
områder helt enige. Det der skiller os er mest at du ser design i yderste
konsekvens hvor jeg måske mere fokuserer på de konkrete behov.
Det har nu været en indsigtsgivende snak



Mogens Hansen (13-05-2007)
Kommentar
Fra : Mogens Hansen


Dato : 13-05-07 20:14


"Janus" <jan@no.mail.no> wrote in message
news:46475bef$0$7604$157c6196@dreader2.cybercity.dk...

[8<8<8<]
> Godt nok er vi enige om at der ikkee r meget at skule designe for små
> systemer, som må køre langsomt, men på den anden side må man også vurdere
> hvad der er af reele behov.

Du antyder at mit design er over kompliceret for små systemer, ved at
separere kollisions håndtering uden for klasserne og gøre klasserne
ligestillede.
Det mener jeg ikke er tilfældet.

Tværtimod lavede jeg med en enkelt template funktion en meget simpel default
implementering, der logger typerne.

[8<8<8<]
> Det der skiller os er mest at du ser design i yderste konsekvens hvor jeg
> måske mere fokuserer på de konkrete behov.

Jeg tror at den store forskel er at jeg på forhånd kendte både problem
domainet (dual dispatch), løsnings domainet (C++ og generisk programmering)
og mapningen mellem disse fra flere kilder, bl.a. Andrei Alexandrescu's som
jeg henviste til

--
Venlig hilsen

Mogens Hansen



Mogens Hansen (12-05-2007)
Kommentar
Fra : Mogens Hansen


Dato : 12-05-07 13:28


"Janus" <jan@no.mail.no> wrote in message
news:464377e4$0$21928$157c6196@dreader1.cybercity.dk...

[8<8<8<]
> for(unsigned int i1=i0+1;i1<sceneElements.size();i1++) //start counter

Iøvrigt:

Foretræk at bruge != i stedet for <
for(..; sceneElements.size() != i; ++i)
fordi:
* det stiller generelt mindre krav til typen af i
* det fejler mere åbenlyst og højlydt, hvis man har fået lavet en fejl i
logikken


--
Venlig hilsen

Mogens Hansen



Janus (12-05-2007)
Kommentar
Fra : Janus


Dato : 12-05-07 13:38

> Foretræk at bruge != i stedet for <
> for(..; sceneElements.size() != i; ++i)
> fordi:
> * det stiller generelt mindre krav til typen af i
> * det fejler mere åbenlyst og højlydt, hvis man har fået lavet en fejl i
> logikken

Jah. Det er vel egentlig en okpointe. Man bør vel alligevel ikke forvente at
i bliver skruet frem over stoppunktet som i

for(int i=0;i!=10;i++)
{
bla bla
i+=3
}

som jo vil køre uendeligt med i=0,4,8,12,......

Er det rimeligt at antage at compileren i
" for(..; sceneElements.size() != i; ++i)"

ikke læser elements.Sice() hver gang, men hiver værdien ud i en variabel
udenfor loopet? Jeg vil tro at en moderne compiler er klog nok, men jeg tør
aldrig rigtig stole på det.... men... ser lige at det var hvad jeg gjorde
denne gang. Dang. Det ville jeg normalt aldrig gøre.

for(unsigned int i0=0;i0<sceneElements.size();i0++)

ville være

unsigned int size=sceneElements.Size();
for(unsigned in i=0;i<size;i++)




Mogens Hansen (12-05-2007)
Kommentar
Fra : Mogens Hansen


Dato : 12-05-07 14:20


"Janus" <jan@no.mail.no> wrote in message
news:4645b520$0$21927$157c6196@dreader1.cybercity.dk...

[8<8<8<]
> Jah. Det er vel egentlig en okpointe.

Det er helt essentielt for at skrive robust kode:
lad det fejle højlydt, i stedet for at forsøge at snige sig videre.

[8<8<8<]
> Er det rimeligt at antage at compileren i
> " for(..; sceneElements.size() != i; ++i)"
>
> ikke læser elements.Sice() hver gang,

Det ville jeg ikke antage.

Bemærk iøvrigt at jeg skrev det så det konstante udtryk står på venstre
side.
Det gør at compileren vil fange, hvis man ved en fejl glemmer "!" og får
skrevet:
sceneElements.size() = i
i modsætning til, hvis konstant udtrykket stod til højre
i = sceneElements.size()


--
Venlig hilsen

Mogens Hansen



Janus (12-05-2007)
Kommentar
Fra : Janus


Dato : 12-05-07 14:47

> Det ville jeg ikke antage.
Det vil jeg fortsætte med heller ikke at atnage så.
Jeg husker dog at have læst et tip om i .net at netop skrive
for(int i=0;i<container.Size;i++)
gør noget med container[i];
og ikke hive size ud som variabel.

Det lader compileren forstå at det er et loop over alle elementer i
container, hvad det ikke gør hvis den er udenfor. Det skulle kunne gøre
koden mere optimal, da det så ikke er nødvendigt at lave range check i
linien "gør noget med container[i];" eftersom i altis vil være in range.
Nu er .net og alm c++ bare ikek det samme, så...

> Bemærk iøvrigt at jeg skrev det så det konstante udtryk står på venstre
> side.

Bemærkede det godt. Det er også klogt nok for med <= får man samme problem
med i=size==true0>evig løkke. Det er en fornuftig ting som jeg har kendt for
evigt, men aldrig rigtig taget til mig. Nok mest fordi det, selvom det giver
det samme, er lidt omvendt at sammenligne size med i, da det er i man er
intereseret i. Jeg er med på at man får samme effekt med a>b og b>a, men i
farten er det mere naturligt at skrive if(den var vi snakker om < en
konstant) end omvendt.



Ukendt (14-05-2007)
Kommentar
Fra : Ukendt


Dato : 14-05-07 18:11


>
> Det lader compileren forstå at det er et loop over alle elementer i
> container, hvad det ikke gør hvis den er udenfor.
>

Loop over alle elementer i C# .Net ?
For Each ??

(super tråd i øvrigt, - måske med undtagelse af alle de "lektier" jeg fik
for mht bøger/links ...)


tpt



Ukendt (14-05-2007)
Kommentar
Fra : Ukendt


Dato : 14-05-07 19:59


>
> Loop over alle elementer i C# .Net ?
> For Each ??
>

Ved andet kig så jeg at der ikke stod C# et eneste sted, sorry



Mogens Hansen (14-05-2007)
Kommentar
Fra : Mogens Hansen


Dato : 14-05-07 20:42


"Troels Thomsen" <nej tak ...> wrote in message
news:4648b176$0$52101$edfadb0f@dread11.news.tele.dk...
>
>>
>> Loop over alle elementer i C# .Net ?
>> For Each ??
>>
>
> Ved andet kig så jeg at der ikke stod C# et eneste sted, sorry

Til gengæld er for_each også på vej ind i C++

--
Venlig hilsen

Mogens Hansen



Janus (14-05-2007)
Kommentar
Fra : Janus


Dato : 14-05-07 22:19

> Loop over alle elementer i C# .Net ?
> For Each ??

Ja. Og med hensyn til din anden posting, så står der faktisk c# et sted. Der
står i hvert fald .net., så du har ret.

foreach er det klar valg, men der er nogle problemer (med mindre jeg da er
helt gak....en mulighed).
Eksempelvis kan du ikke

int[] data=new int[10];
foreach(int integer in data)
integer=12;

Den er readonly. Du kan iistedet
for(int i=0;i<data.Length;i++)
data[i]=12;

> (super tråd i øvrigt, - måske med undtagelse af alle de "lektier" jeg fik
> for mht bøger/links ...)

Ja, jeg synes også jeg fik en del der skulle læses.

øvrigt positivt hvis c++ er ved at få en foreach. Det er da langt mere
læsevenligt og sikkert end en for-løkke.



Thorsten Ottosen (14-05-2007)
Kommentar
Fra : Thorsten Ottosen


Dato : 14-05-07 22:35

Janus skrev:
>> Loop over alle elementer i C# .Net ?
>> For Each ??
>
> Ja. Og med hensyn til din anden posting, s� st�r der faktisk c# et sted. Der
> st�r i hvert fald .net., s� du har ret.
>
> foreach er det klar valg, men der er nogle problemer (med mindre jeg da er
> helt gak....en mulighed).
> Eksempelvis kan du ikke
>
> int[] data=new int[10];
> foreach(int integer in data)
> integer=12;
>
> Den er readonly.

I C++ vil det selvfølgelig være muligt at have en reference her:

int data[10];
for( int& x : data )
x = 12;

[snip]

�vrigt positivt hvis c++ er ved at f� en foreach. Det er da langt mere
> l�sevenligt og sikkert end en for-l�kke.

Se

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2243.html

og indtil da, så kan vi glæde os over Eric Niebler's fremragende

http://www.boost.org/doc/html/foreach.html

netop released i Boost 1.34

-Thorsten

Mogens Hansen (12-05-2007)
Kommentar
Fra : Mogens Hansen


Dato : 12-05-07 15:33


"Janus" <jan@no.mail.no> wrote in message
news:464377e4$0$21928$157c6196@dreader1.cybercity.dk...

[8<8<8<]
> Kan du give et kort eksempel på hvordan man griber det an? Jeg løber hele
> tiden panden mod muren når jeg forsøger atlave et effektivt gennemløb af
> flere containere med hver deres type, eller prøver at stoppe flere
> forskellige objekter ind i samme container.

Den håndkodede, lige-ud-af-landevejen løsning er (testet med Microsoft
Visual C++ 2005):
#include <iostream>
#include <vector>
#include <utility>
#include <cassert>

using namespace std;

class geometry
{
public:
virtual ~geometry(); // Enable RTTI

protected:
// disallow construction - it's an abstract base-class
geometry();

private:
// disable copy constructor and copy assignment
geometry(geometry const&);
geometry& operator=(geometry const&);
};

class box : public geometry
{
public:
box();
};

class sphere : public geometry
{
public:
sphere();
};

class tetrahedron : public geometry
{
public:
tetrahedron();
};

geometry::geometry()
{
}

geometry:geometry()
{
}

box::box()
{
}

sphere::sphere()
{
}

tetrahedron::tetrahedron()
{
}

// The "process" algorithm depends on _all_ geometry types
// but the various geometry types does not depend on each other
// This is almost minimal coupling and reflects the problems domain
void process(geometry const& geo_1, geometry const& geo_2)
{
if(box const* box_1 = dynamic_cast<box const*>(&geo_1)) {
cout << "box<->";
if(box const* box_2 = dynamic_cast<box const*>(&geo_2)) {
cout << "box";
}
else if(sphere const* sphere_2 = dynamic_cast<sphere const*>(&geo_2))
{
cout << "sphere";
}
else if(tetrahedron const* tetrahedron_2 = dynamic_cast<tetrahedron
const*>(&geo_2)) {
cout << "tetrahedron";
}
else {
assert(!"Unexpected type");
}
cout << " collision" << endl;
}
else if(sphere const* sphere_1 = dynamic_cast<sphere const*>(&geo_1)) {
cout << "sphere<->";
if(box const* box_2 = dynamic_cast<box const*>(&geo_2)) {
cout << "box";
}
else if(sphere const* sphere_2 = dynamic_cast<sphere const*>(&geo_2))
{
cout << "sphere";
}
else if(tetrahedron const* tetrahedron_2 = dynamic_cast<tetrahedron
const*>(&geo_2)) {
cout << "tetrahedron";
}
else {
assert(!"Unexpected type");
}
cout << " collision" << endl;
}
else if(tetrahedron const* tetrahedron_1 = dynamic_cast<tetrahedron
const*>(&geo_1)) {
cout << "tetrahedron<->";
cout << "sphere<->";
if(box const* box_2 = dynamic_cast<box const*>(&geo_2)) {
cout << "box";
}
else if(sphere const* sphere_2 = dynamic_cast<sphere const*>(&geo_2))
{
cout << "sphere";
}
else if(tetrahedron const* tetrahedron_2 = dynamic_cast<tetrahedron
const*>(&geo_2)) {
cout << "tetrahedron";
}
else {
assert(!"Unexpected type");
}
cout << " collision" << endl;
}
else {
assert(!"Unexpected type");
}
}

template <typename T>
vector<pair<T*, T*> > broad_phase(const vector<T*>& scene)
{
const vector<T*>::size_type scene_size = scene.size();
vector<pair<T*, T*> > result;
result.reserve(scene_size * (scene_size-1));

for(vector<T*>::const_iterator i = scene.begin(); scene.end() != i; ++i)
{
for(vector<T*>::const_iterator j = i + 1; scene.end() != j; ++j) {
result.push_back(make_pair(*i, *j));
}
}

return result;
}

template <typename T>
void process(const vector<T*>& scene)
{
const vector<pair<T*, T*> > scene_pair = broad_phase(scene);

for(vector<pair<T*, T*> >::const_iterator i = scene_pair.begin();
scene_pair.end() != i; ++i) {
assert(i->first);
assert(i->second);
process(*i->first, *i->second);
}
}

int main()
{
sphere geo_1;
box geo_2;
tetrahedron geo_3;

vector<geometry*> scene;
scene.push_back(&geo_1);
scene.push_back(&geo_2);
scene.push_back(&geo_3);
scene.push_back(&geo_1); // allow processing of same types
scene.push_back(&geo_2);
scene.push_back(&geo_3);

process(scene);
}

hvor process funktionen med de mange dynamic_cast er "tedious and
error-prone" og skalerer elendigt ved mange typer, men koblingen i systemet
er ikke dårlig.

Man kan generere process funktionen med templates og template meta
programming:, med fuldstændigt samme performance og absolut minimal kobling:
hver specifik process funktion afhænger kun af viden om de 2 typer den skal
håndtere og en af geometry klasserne afhænger af hinanden



Mogens Hansen (12-05-2007)
Kommentar
Fra : Mogens Hansen


Dato : 12-05-07 16:54


"Janus" <jan@no.mail.no> wrote in message
news:464377e4$0$21928$157c6196@dreader1.cybercity.dk...

[8<8<8<]
> Kan du give et kort eksempel på hvordan man griber det an? Jeg løber hele
> tiden panden mod muren når jeg forsøger atlave et effektivt gennemløb af
> flere containere med hver deres type, eller prøver at stoppe flere
> forskellige objekter ind i samme container.

Den håndkodede, lige-ud-af-landevejen løsning er (testet med Microsoft
Visual C++ 2005):
#include <iostream>
#include <vector>
#include <utility>
#include <cassert>

using namespace std;

class geometry
{
public:
virtual ~geometry(); // Enable RTTI

protected:
// disallow construction - it's an abstract base-class
geometry();

private:
// disable copy constructor and copy assignment
geometry(geometry const&);
geometry& operator=(geometry const&);
};

class box : public geometry
{
public:
box();
};

class sphere : public geometry
{
public:
sphere();
};

class tetrahedron : public geometry
{
public:
tetrahedron();
};

geometry::geometry()
{
}

geometry:geometry()
{
}

box::box()
{
}

sphere::sphere()
{
}

tetrahedron::tetrahedron()
{
}

// The "process" algorithm depends on _all_ geometry types
// but the various geometry types does not depend on each other
// This is almost minimal coupling and reflects the problems domain
void process(geometry const& geo_1, geometry const& geo_2)
{
if(box const* box_1 = dynamic_cast<box const*>(&geo_1)) {
cout << "box<->";
if(box const* box_2 = dynamic_cast<box const*>(&geo_2)) {
cout << "box";
}
else if(sphere const* sphere_2 = dynamic_cast<sphere const*>(&geo_2))
{
cout << "sphere";
}
else if(tetrahedron const* tetrahedron_2 = dynamic_cast<tetrahedron
const*>(&geo_2)) {
cout << "tetrahedron";
}
else {
assert(!"Unexpected type");
}
cout << " collision" << endl;
}
else if(sphere const* sphere_1 = dynamic_cast<sphere const*>(&geo_1)) {
cout << "sphere<->";
if(box const* box_2 = dynamic_cast<box const*>(&geo_2)) {
cout << "box";
}
else if(sphere const* sphere_2 = dynamic_cast<sphere const*>(&geo_2))
{
cout << "sphere";
}
else if(tetrahedron const* tetrahedron_2 = dynamic_cast<tetrahedron
const*>(&geo_2)) {
cout << "tetrahedron";
}
else {
assert(!"Unexpected type");
}
cout << " collision" << endl;
}
else if(tetrahedron const* tetrahedron_1 = dynamic_cast<tetrahedron
const*>(&geo_1)) {
cout << "tetrahedron<->";
if(box const* box_2 = dynamic_cast<box const*>(&geo_2)) {
cout << "box";
}
else if(sphere const* sphere_2 = dynamic_cast<sphere const*>(&geo_2))
{
cout << "sphere";
}
else if(tetrahedron const* tetrahedron_2 = dynamic_cast<tetrahedron
const*>(&geo_2)) {
cout << "tetrahedron";
}
else {
assert(!"Unexpected type");
}
cout << " collision" << endl;
}
else {
assert(!"Unexpected type");
}
}

template <typename T>
vector<pair<T*, T*> > broad_phase(const vector<T*>& scene)
{
const vector<T*>::size_type scene_size = scene.size();
vector<pair<T*, T*> > result;
result.reserve(scene_size * (scene_size-1));

for(vector<T*>::const_iterator i = scene.begin(); scene.end() != i; ++i)
{
for(vector<T*>::const_iterator j = i + 1; scene.end() != j; ++j) {
result.push_back(make_pair(*i, *j));
}
}

return result;
}

template <typename T>
void process(const vector<T*>& scene)
{
const vector<pair<T*, T*> > scene_pair = broad_phase(scene);

for(vector<pair<T*, T*> >::const_iterator i = scene_pair.begin();
scene_pair.end() != i; ++i) {
assert(i->first);
assert(i->second);
process(*i->first, *i->second);
}
}

int main()
{
sphere geo_1;
box geo_2;
tetrahedron geo_3;

vector<geometry*> scene;
scene.push_back(&geo_1);
scene.push_back(&geo_2);
scene.push_back(&geo_3);
scene.push_back(&geo_1); // allow processing of same types
scene.push_back(&geo_2);
scene.push_back(&geo_3);

process(scene);
}

hvor process funktionen med de mange dynamic_cast er "tedious and
error-prone" og skalerer elendigt ved mange typer, men koblingen i systemet
er ikke dårlig.

Man kan generere process funktionen med templates og template meta
programming:, med fuldstændigt samme performance og absolut minimal kobling:
hver specifik process funktion afhænger kun af viden om de 2 typer den skal
håndtere og ingen af geometry klasserne afhænger af hinanden.
Alle geometry typer er ligestillet og kræver ikke recompilering når nye
kommer og går (testet med Microsoft Visual C++ 2005).

#include <iostream>
#include <vector>
#include <utility>
#include <typeinfo>
#include <cassert>

using namespace std;

class null_type;

template <typename T, typename U>
struct type_list_impl
{
typedef T head;
typedef U tail;
};

template <typename T1, typename T2 = null_type, typename T3 = null_type,
typename T4 = null_type>
struct type_list;

template <typename T1, typename T2, typename T3>
struct type_list<T1, T2, T3, null_type>
{
typedef type_list_impl<T1, typename type_list<T2, T3>::list >
list;
};

template <typename T1, typename T2>
struct type_list<T1, T2, null_type>
{
typedef type_list_impl<T1, typename type_list<T2>::list >
list;
};

template <typename T1>
struct type_list<T1, null_type, null_type>
{
typedef type_list_impl<T1, null_type>
list;
};

class geometry
{
public:
virtual ~geometry(); // Enable RTTI

protected:
// disallow construction - it's an abstract base-class
geometry();

private:
// disable copy constructor and copy assignment
geometry(geometry const&);
geometry& operator=(geometry const&);
};

class box : public geometry
{
public:
box();
};

class sphere : public geometry
{
public:
sphere();
};

class tetrahedron : public geometry
{
public:
tetrahedron();
};

geometry::geometry()
{
}

geometry:geometry()
{
}

box::box()
{
}

sphere::sphere()
{
}

tetrahedron::tetrahedron()
{
}

template <typename T1, typename T2 = T1>
struct process_list;

template <typename T1, typename U1, typename T2, typename U2>
struct process_list<type_list_impl<T1, U1>, type_list_impl<T2, U2> >
{
static void dispatch(geometry const& geo_1, geometry const& geo_2)
{
if(T1 const* t_1 = dynamic_cast<T1 const*>(&geo_1)) {
if(T2 const* t_2 = dynamic_cast<T2 const*>(&geo_2)) {
process(*t_1, *t_2);
}
else {
process_list<type_list_impl<T1, null_type>, U2>::dispatch(*t_1,
geo_2);
}
}
else {
process_list<U1, type_list_impl<T2, U2> >::dispatch(geo_1, geo_2);
}
}

template <typename T>
static void dispatch(T const& geo_1, geometry const& geo_2)
{
if(T2 const* t_2 = dynamic_cast<T2 const*>(&geo_2)) {
process(geo_1, *t_2);
}
else {
process_list<type_list_impl<T1, U1>, U2>::dispatch(geo_1, geo_2);
}
}
};

template <typename T1, typename T2, typename U2>
struct process_list<type_list_impl<T1, null_type>, type_list_impl<T2, U2> >
{
static void dispatch(geometry const& geo_1, geometry const& geo_2)
{
if(T1 const* t_1 = dynamic_cast<T1 const*>(&geo_1)) {
if(T2 const* t_2 = dynamic_cast<T2 const*>(&geo_2)) {
process(*t_1, *t_2);
}
else {
process_list<type_list_impl<T1, null_type>, U2>::dispatch(t_1,
geo_2);
}
}
else {
assert(!"Unexpected type");
}
}

template <typename T>
static void dispatch(T const& geo_1, geometry const& geo_2)
{
if(T2 const* t_2 = dynamic_cast<T2 const*>(&geo_2)) {
process(geo_1, *t_2);
}
else {
process_list<type_list_impl<T, null_type>, U2>::dispatch(geo_1,
geo_2);
}
}
};

template <typename T1, typename T2>
struct process_list<type_list_impl<T1, null_type>, type_list_impl<T2,
null_type> >
{
static void dispatch(geometry const& geo_1, geometry const& geo_2)
{
if(T1 const* t_1 = dynamic_cast<T1 const*>(&geo_1)) {
if(T1 const* t_2 = dynamic_cast<T1 const*>(&geo_2)) {
process(*t_1, *t_2);
}
else {
assert(!"Unexpected type");
}
}
else {
assert(!"Unexpected type");
}
}

template <typename T>
static void dispatch(T const& geo_1, geometry const& geo_2)
{
if(T2 const* t_2 = dynamic_cast<T2 const*>(&geo_2)) {
process(geo_1, *t_2);
}
else {
assert(!"Unexpected type");
}
}
};

class reentrance_lock
{
public:
reentrance_lock(bool& locked) :
locked_(locked)
{
assert(!locked);
locked_ = true;
}

~reentrance_lock()
{
locked_ = false;
}

private:
bool& locked_;

private:
// disable copy construction and copy assignment
reentrance_lock(reentrance_lock const&);
reentrance_lock& operator=(reentrance_lock const&);
};

template <typename T1, typename T2>
inline void process(T1 const& t_1, T2 const& t_2)
{
static bool reentered = false;
if(reentered) {
cout << "General: " << typeid(T1).name() << "<->" << typeid(T2).name()
<< endl;
return;
}
reentrance_lock lock(reentered);
// handle symmetry
process(t_2, t_1);
}

void process(sphere const& /*sphere_obj*/, box const& /*box_obj*/)
{
std::cout << "Special sphere<->box" << std::endl;
}

// The "process" algorithm depends on _all_ geometry types
// but the various geometry types does not depend on each other
// This is almost minimal coupling and reflects the problems domain
void process(geometry const& geo_1, geometry const& geo_2)
{
typedef type_list<
box,
sphere,
tetrahedron>
geometry_types;

process_list<geometry_types::list>::dispatch(geo_1, geo_2);
}

template <typename T>
vector<pair<T*, T*> > broad_phase(const vector<T*>& scene)
{
const vector<T*>::size_type scene_size = scene.size();
vector<pair<T*, T*> > result;
result.reserve(scene_size * (scene_size-1));

for(vector<T*>::const_iterator i = scene.begin(); scene.end() != i; ++i)
{
for(vector<T*>::const_iterator j = i + 1; scene.end() != j; ++j) {
result.push_back(make_pair(*i, *j));
}
}

return result;
}

template <typename T>
void process(const vector<T*>& scene)
{
const vector<pair<T*, T*> > scene_pair = broad_phase(scene);

for(vector<pair<T*, T*> >::const_iterator i = scene_pair.begin();
scene_pair.end() != i; ++i) {
assert(i->first);
assert(i->second);
process(*i->first, *i->second);
}
}

int main()
{
sphere geo_1;
box geo_2;
tetrahedron geo_3;

vector<geometry*> scene;
scene.push_back(&geo_1);
scene.push_back(&geo_2);
scene.push_back(&geo_3);
scene.push_back(&geo_1); // allow processing of same types
scene.push_back(&geo_2);
scene.push_back(&geo_3);

process(scene);
}


Der er det paradoksale i disse objekt-orienterede tider, at globale
funktioner giver mindst kobling, og nedarvning giver den hårdeste kobling.

Bemærk også hvordan en række forskellige paradigmer arbejder stærkt sammen
med tilsammen at give løsningen:
* objekt-orienteret programmering i geometry typerne
* procedural programmering til håndtering af kollision
* generisk programmering sammen med overload til håndtering af symmetry
* template meta-programmering til at lave dual-dispatch

--
Venlig hilsen

Mogens Hansen



Mogens Hansen (11-05-2007)
Kommentar
Fra : Mogens Hansen


Dato : 11-05-07 16:22


"Mogens Hansen" <mogens_h@dk-online.dk> wrote in message
news:464353d6$0$7609$157c6196@dreader2.cybercity.dk...

[8<8<8<]
> Det er faktisk muligt at skrive noget template kode, som automatisk
> genererer optimal hurtig kode.

Nedenstående kode tager udgangspunkt i noget kode jeg skrev i 2001, som svar
på et spørgsmål Scott Meyers stillede
http://kortlink.dk/3uu6

Lige et par advarsler:
* det er ikke trivielt
* det bruger ikke virtuelle metoder, men sin egen implementer run-time
type information med det formål at sikre hurtig look-up
* det er problematisk i forbindelse med dynamisk linkning
* jeg vil ikke anbefale at du skriver det af som opgave løsning - det som
står i Alexandrescu's bog er mere lige-ud-af-landevejen
typelist er essentiel for at lave en fornuftig løsning - og det er ikke
noget man selv opfinder fra bunden

Designet indeholder et par forskellige uafhængige elementer:
* typelist - til at specificere et container af typer. Rimeligt
almindeligt i template meta programmering
* et framework, der automatisk sikrer at hver klasse får sig unikke,
automatisk genereret ID i form af en integer
* et framework, der automatisk opbygger en 2-dimensionel jump-tabel på
run-time (det kan vist ikke gøres på compile-time)
* der er inkluderet policy klasser til at kunne bestemme hvordan de
nødvendige cast skal laves og hvad der skal ske hvis man laver dual-dispatch
på en ukendt klasse
* applikations kode, med klasser der benytter frameworket til sikre unikke
ID'er
* applikations kode der starter dual-dispatch kaldet
* applikations kode der eksekverer funktionalitet efter typerne er bestemt

Bemærk også at containeren indeholder pointere til basis-typen - altså en
homogen container.

Koden er testet med Visual C++ 2005 og Borland C++V6 på MS-Windows.
Du kan prøve at single steppe gennem koden for at finde ud af hvad der sker


#include <iostream>
#include <vector>
#include <typeinfo>
#include <utility>
#include <cassert>

// Some typelist stuff
// Very much like
// Modern C++ Design
// Andrei Alexandrescu
// ISBN 0-201-70431
// but without preprocessor macros

class null_type;

template <typename T, typename U>
struct type_list_impl
{
typedef T head;
typedef U tail;
};

template <typename T1, typename T2 = null_type, typename T3 = null_type,
typename T4 = null_type>
struct type_list;

template <typename T1, typename T2, typename T3>
struct type_list<T1, T2, T3, null_type>
{
typedef type_list_impl<T1, typename type_list<T2, T3>::list >
list;
};

template <typename T1, typename T2>
struct type_list<T1, T2, null_type>
{
typedef type_list_impl<T1, typename type_list<T2>::list >
list;
};

template <typename T1>
struct type_list<T1, null_type, null_type>
{
typedef type_list_impl<T1, null_type>
list;
};

//external polymorphic framework

template <typename T>
class external_polymorphic
{
public:
unsigned id() const;
static unsigned max_class_id();

protected:
external_polymorphic(unsigned id);

static unsigned register_class();

private:
static unsigned& class_count();

private:
const unsigned id_;
};

template <typename T, typename D>
class reg_external_polymorphic : public T
{
public:
static unsigned class_id();

protected:
reg_external_polymorphic();

template <typename ARG1>
reg_external_polymorphic(ARG1 arg1);

template <typename ARG1, typename ARG2>
reg_external_polymorphic(ARG1 arg1, ARG2 arg2);

template <typename ARG1, typename ARG2, typename ARG3>
reg_external_polymorphic(ARG1 arg1, ARG2 arg2, ARG3 arg3);

template <typename ARG1, typename ARG2, typename ARG3, typename ARG4>
reg_external_polymorphic(ARG1 arg1, ARG2 arg2, ARG3 arg3, ARG4 arg4);

~reg_external_polymorphic();

private:
// ensure that class_id is called, at latest before main is entered
// so that all classes are registered
static const unsigned class_id_;
};

template <typename T, typename D>
const unsigned reg_external_polymorphic<T,D>::class_id_ =
reg_external_polymorphic<T,D>::class_id();

template <typename T>
inline external_polymorphic<T>::external_polymorphic(unsigned id) :
id_(id)
{
T* t = static_cast<T*>(this);
t; // used
}

template <typename T>
inline unsigned external_polymorphic<T>::id() const
{
return id_;
}

template <typename T>
inline unsigned external_polymorphic<T>::max_class_id()
{
return class_count();
}

template <typename T>
inline unsigned& external_polymorphic<T>::class_count()
{
static unsigned class_count_ = 0;
return class_count_;
}

// made non-inline due to bug in C++Builder V6.0 code generation
template <typename T>
inline unsigned external_polymorphic<T>::register_class()
{
return class_count()++;
}

template <typename T, typename D>
inline reg_external_polymorphic<T, D>::reg_external_polymorphic() :
T(class_id())
{
}

template <typename T, typename D>
template <typename ARG1>
inline reg_external_polymorphic<T, D>::reg_external_polymorphic(ARG1 arg1) :
T(class_id(), arg1)
{
}

template <typename T, typename D>
template <typename ARG1, typename ARG2>
inline reg_external_polymorphic<T, D>::reg_external_polymorphic(ARG1 arg1,
ARG2 arg2) :
T(class_id(), arg1, arg2)
{
}

template <typename T, typename D>
template <typename ARG1, typename ARG2, typename ARG3>
inline reg_external_polymorphic<T, D>::reg_external_polymorphic(ARG1 arg1,
ARG2 arg2, ARG3 arg3) :
T(class_id(), arg1, arg2, arg3)
{
}

template <typename T, typename D>
template <typename ARG1, typename ARG2, typename ARG3, typename ARG4>
inline reg_external_polymorphic<T, D>::reg_external_polymorphic(ARG1 arg1,
ARG2 arg2, ARG3 arg3, ARG4 arg4) :
T(class_id(), arg1, arg2, arg3, arg4)
{
}

template <typename T, typename D>
inline reg_external_polymorphic<T, D>:reg_external_polymorphic()
{
// D must be derived from this class
reg_external_polymorphic<T, D>* d = static_cast<D*>(0);
d; //used!

// This class must be derived from external_polymorphic<T>
external_polymorphic<T>* ept = this;
ept; //used!!

&class_id_; // ensure class_id_ is reference and not optimized away !
}

template <typename T, typename D>
inline unsigned reg_external_polymorphic<T, D>::class_id()
{
static const unsigned class_id_ =
external_polymorphic<T>::register_class();
return class_id_;
}

// "external polymorphism"
// see Bjarne Stroustrup slides
// "Design Decisions for a Library"
// http://www.klid.dk/arrangementer/XTI_kbh.pdf
// slide 14-18
//
// this vcall template class is modelled after those slides

// cast policy classes
template <typename T1>
struct do_static_cast
{
template <typename T2>
T2 do_cast(T1 t) const
{ return static_cast<T2>(t); }
};

template <typename T1>
struct do_dynamic_cast
{
template <typename T2>
T2 do_cast(T1 t) const
{ return dynamic_cast<T2>(t); }
};

// pure virtual function call policy
template <typename ReturnType>
struct pure_virtual_throw
{
template <typename ARG0, typename ARG1>
static ReturnType pure_virtual(ARG0 /*arg0*/, ARG1 /*arg1*/)
{ throw 0; /* or something more sensible */ }
};

template <typename ReturnType>
struct pure_virtual_nothing
{
template <typename ARG0, typename ARG1>
static ReturnType pure_virtual(ARG0 /*arg0*/, ARG1 /*arg1*/)
{ }
};

template <typename Func, typename T1, typename T2, typename CastPolicy>
struct dual_dispatch_table_generator_impl;

template <typename Func, typename DispatchTypelist, typename CastPolicy>
struct dual_dispatch_table_generator
{
typedef typename Func::return_type return_type;
typedef typename Func::param_type param_type;
typedef return_type (*func_ptr_type)(param_type const&, param_type
const&);
typedef std::vector<std::vector<func_ptr_type> > func_vector_type;

static void fill_func_vector(func_vector_type& func_vector)
{ dual_dispatch_table_generator_impl<Func, typename
DispatchTypelist::list, typename DispatchTypelist::list,
CastPolicy>::fill_func_vector(func_vector); }

static unsigned max_class_id()
{ return dual_dispatch_table_generator_impl<Func, typename
DispatchTypelist::list, typename DispatchTypelist::list,
CastPolicy>::max_class_id(); }
};

template <typename Func, typename T1, typename T2, typename CastPolicy>
struct dual_dispatch_table_generator_impl<Func, type_list_impl<T1,
null_type>, type_list_impl<T2, null_type>, CastPolicy>
{
typedef typename Func::return_type return_type;
typedef typename Func::param_type param_type;
typedef return_type (*func_ptr_type)(param_type const&, param_type
const&);
typedef std::vector<std::vector<func_ptr_type> > func_vector_type;

static return_type do_call(param_type const& param_1, param_type const&
param_2)
{ return Func()(CastPolicy().template do_cast<T1 const&>(param_1),
CastPolicy().template do_cast<T2 const&>(param_2)); }

static void fill_func_vector(func_vector_type& func_vector)
{
func_vector[T1::class_id()][T2::class_id()] = do_call;
}

static unsigned max_class_id()
{ return T1::max_class_id(); }
};

template <typename Func, typename T1, typename T2, typename U2, typename
CastPolicy>
struct dual_dispatch_table_generator_impl<Func, type_list_impl<T1,
null_type>, type_list_impl<T2, U2>, CastPolicy>
{
typedef typename Func::return_type return_type;
typedef typename Func::param_type param_type;
typedef return_type (*func_ptr_type)(param_type const&, param_type
const&);
typedef std::vector<std::vector<func_ptr_type> > func_vector_type;

static return_type do_call(param_type const& param_1, param_type const&
param_2)
{ return Func()(CastPolicy().template do_cast<T1 const&>(param_1),
CastPolicy().template do_cast<T2 const&>(param_2)); }

static void fill_func_vector(func_vector_type& func_vector)
{
func_vector[T1::class_id()][T2::class_id()] = do_call;
dual_dispatch_table_generator_impl<Func, type_list_impl<T1,
null_type>, U2, CastPolicy>::fill_func_vector(func_vector);
}

static unsigned max_class_id()
{
return T1::max_class_id();
}
};

template <typename Func, typename T1, typename U1, typename T2, typename
CastPolicy>
struct dual_dispatch_table_generator_impl<Func, type_list_impl<T1, U1>,
type_list_impl<T2, null_type>, CastPolicy>
{
typedef typename Func::return_type return_type;
typedef typename Func::param_type param_type;
typedef return_type (*func_ptr_type)(param_type const&, param_type
const&);
typedef std::vector<std::vector<func_ptr_type> > func_vector_type;

static return_type do_call(param_type const& param_1, param_type const&
param_2)
{ return Func()(CastPolicy().template do_cast<T1 const&>(param_1),
CastPolicy().template do_cast<T2 const&>(param_2)); }

static void fill_func_vector(func_vector_type& func_vector)
{
func_vector[T1::class_id()][T2::class_id()] = do_call;
dual_dispatch_table_generator_impl<Func, U1, type_list_impl<T2,
null_type>, CastPolicy>::fill_func_vector(func_vector);
}

static unsigned max_class_id()
{
return T1::max_class_id();
}
};

template <typename Func, typename T1, typename U1, typename T2, typename U2,
typename CastPolicy>
struct dual_dispatch_table_generator_impl<Func, type_list_impl<T1, U1>,
type_list_impl<T2, U2>, CastPolicy>
{
typedef typename Func::return_type return_type;
typedef typename Func::param_type param_type;
typedef return_type (*func_ptr_type)(param_type const&, param_type
const&);
typedef std::vector<std::vector<func_ptr_type> > func_vector_type;

static return_type do_call(param_type const& param_1, param_type const&
param_2)
{ return Func()(CastPolicy().template do_cast<T1 const&>(param_1),
CastPolicy().template do_cast<T2 const&>(param_2)); }

static void fill_func_vector(func_vector_type& func_vector)
{
func_vector[T1::class_id()][T2::class_id()] = do_call;
dual_dispatch_table_generator_impl<Func, type_list_impl<T1,
null_type>, U2, CastPolicy>::fill_func_vector(func_vector);
dual_dispatch_table_generator_impl<Func, U1, type_list_impl<T2,
null_type>, CastPolicy>::fill_func_vector(func_vector);
dual_dispatch_table_generator_impl<Func, U1, U2,
CastPolicy>::fill_func_vector(func_vector);
dual_dispatch_table_generator_impl<Func, U2, U1,
CastPolicy>::fill_func_vector(func_vector);
}

static unsigned max_class_id()
{
return T1::max_class_id();
}
};

template <typename Func, typename DispatchTypelist, typename
PureVirtualPolicy, typename CastPolicy>
struct dual_dispatch_func_table
{
typedef typename Func::return_type return_type;
typedef typename Func::param_type param_type;
typedef return_type (*func_ptr_type)(param_type const&, param_type
const&);
typedef std::vector<std::vector<func_ptr_type> > func_vector_type;
typedef dual_dispatch_table_generator<Func, DispatchTypelist, CastPolicy>
dual_dispatch_table_generator_type;

dual_dispatch_func_table();

func_vector_type const& func_vector() const
{ return func_vector_; }

static return_type pure_virtual(param_type const& param_1, param_type
const& param_2)
{ return PureVirtualPolicy().pure_virtual<param_type const&, param_type
const&>(param_1, param_2); }

static unsigned max_class_id()
{ return dual_dispatch_table_generator_type::max_class_id(); }

private:
func_vector_type func_vector_;

private:
dual_dispatch_func_table(dual_dispatch_func_table const&);
dual_dispatch_func_table& operator=(dual_dispatch_func_table const&);
};

// constructor is not inlined - only called once
// code bloat if inlined
template <typename Func, typename DispatchTypelist, typename
PureVirtualPolicy, typename CastPolicy>
dual_dispatch_func_table<Func, DispatchTypelist, PureVirtualPolicy,
CastPolicy>::dual_dispatch_func_table() :
func_vector_(max_class_id(), std::vector<func_ptr_type>(max_class_id(),
pure_virtual))
{
dual_dispatch_table_generator_type::fill_func_vector(func_vector_);
}

template <typename Func, typename DispatchTypelist, typename
PureVirtualPolicy, typename CastPolicy>
inline typename Func::return_type dual_dispatch_call(typename
Func::param_type const& param_1, typename Func::param_type const& param_2)
{
typedef typename dual_dispatch_func_table<Func, DispatchTypelist,
PureVirtualPolicy, CastPolicy> dual_dispatch_func_table_type;
typedef typename dual_dispatch_func_table_type::func_vector_type
func_vector_type;

// jump-table initialized first time dual_dispatch_call is called
static const dual_dispatch_func_table_type func_tbl;

return (*func_tbl.func_vector()[param_1.id()][param_2.id()])(param_1,
param_2);
}

template <typename Func, typename DispatchTypelist>
inline typename Func::return_type dual_dispatch_call(typename
Func::param_type const& param_1, typename Func::param_type const& param_2)
{
return dual_dispatch_call<Func, DispatchTypelist,
pure_virtual_throw<typename Func::return_type>, do_static_cast<typename
Func::param_type const&> >(param_1, param_2);
}

template <typename Func, typename DispatchTypelist, typename
PureVirtualPolicy>
inline typename Func::return_type dual_dispatch_call(typename
Func::param_type const& param_1, typename Func::param_type const& param_2)
{
return dual_dispatch_call<Func, DispatchTypelist, PureVirtualPolicy,
do_static_cast<typename Func::param_type const&> >(param_1, param_2);
}

// application code

// The geometry framework

class geometry : public external_polymorphic<geometry>
{
// the destructor _need_ not be virtual
// we do not rely on RTTI

protected:
// destructor made protected to prevent
geometry(unsigned id) :
external_polymorphic<geometry>(id) {}

private:
// copy constructor and copy assignment not implemented
geometry(const geometry&);
geometry& operator=(const geometry&);
};

class sphere : public reg_external_polymorphic<geometry, sphere>
{
public:
sphere() {}
};

class box : public reg_external_polymorphic<geometry, box>
{
public:
box() {}
};

class tetrahedron : public reg_external_polymorphic<geometry, tetrahedron>
{
public:
tetrahedron() {}
};


template <typename BaseT>
struct func_process
{
public:
typedef BaseT param_type;
typedef void return_type;

template <typename DerivedT>
return_type operator()(DerivedT const& t)
{ return process(t); }

template <typename DerivedT1, typename DerivedT2>
return_type operator()(DerivedT1 const& param_1, DerivedT2 const&
param_2)
{ return process(param_1, param_2); }
};

inline void process(geometry const& param_1, geometry const& param_2)
{
typedef type_list<
sphere,
box,
tetrahedron>
geometry_type_list;
typedef func_process<geometry> func;
return dual_dispatch_call<func, geometry_type_list>(param_1, param_2);
}

template <typename T1, typename T2>
void process(T1 const& /*t_1*/, T2 const& /*t_2*/)
{
std::cout << "General: " << typeid(T1).name() << "<->" <<
typeid(T2).name() << std::endl;
}

void process(sphere const& sphere_obj, box const& box_obj)
{
std::cout << "Special sphere<->box" << std::endl;
}

template <typename T>
std::vector<std::pair<T*, T*> > broad_phase(const std::vector<T*>& scene)
{
using std::vector;
using std::pair;
using std::make_pair;

const vector<T*>::size_type scene_size = scene.size();
vector<pair<T*, T*> > result;
result.reserve(scene_size * (scene_size-1));

for(vector<T*>::const_iterator i = scene.begin(); scene.end() != i; ++i)
{
for(vector<T*>::const_iterator j = i + 1; scene.end() != j; ++j) {
result.push_back(make_pair(*i, *j));
}
}

return result;
}

template <typename T>
void process(const std::vector<T*>& scene)
{
using std::vector;
using std::pair;

const vector<pair<T*, T*> > scene_pair = broad_phase(scene);

for(vector<pair<T*, T*> >::const_iterator i = scene_pair.begin();
scene_pair.end() != i; ++i) {
assert(i->first);
assert(i->second);
process(*i->first, *i->second);
}
}

int main()
{
using std::cerr; using std::cout;
using std::endl; using std::vector;

sphere geo_1;
box geo_2;
tetrahedron geo_3;

vector<geometry*> scene;
scene.push_back(&geo_1);
scene.push_back(&geo_2);
scene.push_back(&geo_3);
scene.push_back(&geo_1); // allow processing of same types
scene.push_back(&geo_2);
scene.push_back(&geo_3);

process(scene);
}

--
Venlig hilsen

Mogens Hansen



Søg
Reklame
Statistik
Spørgsmål : 177417
Tips : 31962
Nyheder : 719565
Indlæg : 6407864
Brugere : 218876

Månedens bedste
Årets bedste
Sidste års bedste