Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien
Toon posts:

polymorphism probleem c++

Pagina: 1
Acties:

Verwijderd

Topicstarter
Ik ben een programma aan het maken waarin bewegende objecten tegen elkaar botsen.
Ik heb een base-class Entity gemaakt, en 2 afgeleide klassen Rock en Creature.
Creature heeft de methode "move()" en Rock niet (en Entity virtual).
Ik maak een array van Entity als volgt aan:
[code=c++]
class World
{
array<Entity*>pop;
World()
{
Entity* x=new Creature();
Entity* y=new Rock();
pop.push_back(x);
pop.push_back(y);
}
};
[/code=c++]

Ik heb in Entity ook een functie bool intersects(Entity*e) gedefinieerd die kijkt of 2 entities elkaar raken.
Vervolgens pak ik pop[i], en ga ik elke entity af en kijk of ze ermee intersecten.
Daarvoor wil ik een kopie maken van pop[x], daar move op uitvoeren en kijken of hij dan intersect.
Mijn vraag is nu, hoe maak ik die kopie met behoud van methoden als move, maar zonder dus iets in pop[i] te veranderen.
Dit is wat ik nu doe:
[code=c++]
Entity* e=pop[i];
e->move();
//check op intersects en zo nee, dan:
pop[i].move();
[/code=c++]
Ik wil dus even testen of die move wel valide is.
Weet iemand hoe ik die kopie kan maken zodat het een afgeleid object blijft, maar niet zijn bewerkingen doet in pop[i] ?
Alvast bedankt

PS Ik heb weinig scholing in c++ gehad, en ben mezelf wat bij aan het leren door zelf wat te gaan programmeren.

PPS ik zie dat ik de titel niet juist heb geformat, excuses hiervoor.

[ Voor 2% gewijzigd door Verwijderd op 17-04-2008 17:55 . Reden: PPS ]


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 14:39
Je zou een virtual method Entity *clone() kunnen definiëren; dan moet elke afgeleide klasse die weer implementeren zo dat bij het aanroepen een kopie van het juiste type opgeleverd wordt. Is dat iets?

Verwijderd

Topicstarter
Dat is wellicht een optie, maar het voelt nogal vreemd aan, daar heb je toch operators als "=" voor? Het lijkt me namelijk nogal een basis operatie, een kopie van een pointer naar een afgeleid object maken. Het implementeren van een clone functie voor elke afgeleide lijkt nogal omslachtig. Maar ik weet niet genoeg van de diepere werkingen van c++ om dit te kunnen stellen.

Verwijderd

Een kopie van een pointer, dan verwijzen beide pointers nog steeds naar hetzelfde object.

Een van de manieren om niet-value types (eigen objecten dus) te kopieren is het implementeren van een Clone() methode.

Verder kan je nog een binairy stream maken van je object, in C# gebruik je daar bijvoorbeeld Serialization voor.

Verwijderd

Topicstarter
ok dan wordt het een clone functie voor elke afgeleide, als dat standard practice is,
bedankt mensen

  • writser
  • Registratie: Mei 2000
  • Laatst online: 18-11 12:05
Soultaker's methode lijkt me prima. Je kan niet zo maar een kopie maken met een copy constructor omdat je dan informatie van de afgeleide klassen weggooit. Bovendien is dat gevaarlijk als je klasses zelf resources gaan alloceren. Ik hoop dat je hier iets van snapt. :) Sowieso wil ik je aanraden om een boek over C++ te kopen. Accelerated C++ bijvoorbeeld. Het is nou eenmaal een tricky taal en een goed boek helpt je enorm.

Maar wat misschien nog beter is: het hele probleem omzeilen. Bijvoorbeeld door een functie undoMove() toe te voegen, een functie isValidMove() of iets in die trant. Is het echt nodig dat je een complete kopie maakt elke keer dat je een object een stapje verplaatst?

[ Voor 12% gewijzigd door writser op 17-04-2008 19:33 ]

Onvoorstelbaar!


Verwijderd

Topicstarter
undoMove is inderdaad de beste oplossing, maar toch goed te weten dat een clone-functie soms nodig is, wederom bedankt

Hier nog een uitleg die ik zelf ook had kunnen vinden (als ik de terminologie wat beter beheerste).

[ Voor 42% gewijzigd door Verwijderd op 17-04-2008 20:21 ]


  • redfox314
  • Registratie: December 2004
  • Laatst online: 07-11 14:35
Ik snap trouwens niet waarom je Array<Entity*> doet en niet Array<Entity>.
Use references when you can, and pointers when you have to.
Je haalt je heel de problematiek van memory leaks op de nek. Gebruik geen pointers tenzij je er een specifieke reden toe hebt.
Je moet enkel waar nodig een & voor een variabele zetten om aan te duiden dat je geen kopie van het object wilt meegeven maar een reference.
Je moet dan voor de default constructor niet expliciet instantieren.

C++:
1
Entity* x=new Creature();

wordt dan
C++:
1
Entity c;

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 08:45

.oisyn

Moderator Devschuur®

Demotivational Speaker

redfox314 schreef op donderdag 17 april 2008 @ 20:43:
Ik snap trouwens niet waarom je Array<Entity*> doet en niet Array<Entity>.
Omdat je in een array<Entity> geen derived instances van Entity op kunt slaan natuurlijk.
C++:
1
Entity* x=new Creature();

wordt dan
C++:
1
Entity c;
Ik hoop dat je zelf al het probleem ziet? In het eerste voorbeeld maak je een Creature. In het tweede voorbeeld gewoon een Entity.

Je zou evt. wel kunnen overwegen om smart pointers te gebruiken. Een array<std::tr1::shared_ptr<Entity> > wordt het dan bijvoorbeeld.


Mbt het probleem, ik zie eigenlijk niet in waarom je überhaupt een kopie zou moeten maken. Voor het testen van intersecties zijn neem ik aan niet alle eigenschappen van een Entity van belang. Sterker nog, eigenlijk wil je alleen maar de bounding box oid op de nieuwe plek weten. Die bounding box kun je gebruiken om intersecties te testen.

[ Voor 63% gewijzigd door .oisyn op 17-04-2008 21:39 ]

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


Verwijderd

Topicstarter
smart pointers moet ik nog een keer nazoeken,
voor intersectie heb ik een position2d nodig en de radius, dan kijk ik gewoon of de lengte van de tussenliggende lijn langer is dan de twee radia (radio, radii,radiussen?) opgeteld, dat is mijn intersectie-algoritme. maar dan ben ik wel gebonden aan cirkels, maar die lijken mij ook wel ok. Ligt een beetje aan het type creature.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 08:45

.oisyn

Moderator Devschuur®

Demotivational Speaker

Bounding spheres dus, ook goed :). Stop die in een data structure als je die niet al hebt, en maak op Entity een virtual method die je de eigenschappen van die bounding sphere geeft nadat de Entity zou hebben gewogen. Gebruik die sphere om je tests mee te doen, en als geen enkele andere Entity ermee intersect dan kun je uiteindelijk move() aanroepen. Daarmee is dat hele gedoe van het kopiëren overbodig :)

C++:
1
2
3
4
Sphere sphere = e->getBoundingSphereAfterMove();
// doe intersection tests met sphere
// en indien geen intersectie:
e->move();

[ Voor 16% gewijzigd door .oisyn op 18-04-2008 01:36 ]

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


  • redfox314
  • Registratie: December 2004
  • Laatst online: 07-11 14:35
My mistake.

Anders dan in java heb je in c++ geen polymorfisme zonder pointers.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 08:45

.oisyn

Moderator Devschuur®

Demotivational Speaker

In java heb je niets anders dan pointers als het om objecten gaat. Als je in java
Java:
1
Entity e;

doet, dan maak je nog altijd geen nieuwe Entity. e is dan null.

[ Voor 45% gewijzigd door .oisyn op 18-04-2008 17:31 ]

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


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 16-11 18:33
redfox314 schreef op donderdag 17 april 2008 @ 20:43:
Ik snap trouwens niet waarom je Array<Entity*> doet en niet Array<Entity>.
Omdat je dan last krijgt van object slicing. Bekijk de volgende code maar eens:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <vector>
using namespace std;

struct A
{
    int a;
};

struct B : public A
{
    int b[1024];
};

int main( int arc, char* argv[] )
{
    vector<A> V;

    A a;
    B b;

    V.push_back( a );
    V.push_back( b );
}


Aangezien de vector alleen A objecten kan opslaan wordt alles wat niet in A zit eraf "gesliced". Met pointers heb je dat niet want die zijn allemaal even groot.

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


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 08:45

.oisyn

Moderator Devschuur®

Demotivational Speaker

Zoals je het nu zegt lijkt het alsof je wel deze C erin op zou kunnen slaan:
C++:
1
2
3
struct C : public A
{
};

omdat in de praktijk sizeof(C) == sizeof(A). Dat is natuurlijk ook niet zo :). In V kun je alleen maar letterlijke A's opslaan, het is immers een vector<A>. Als je een B of C cast naar een A, dan wordt het een A (dat is wat casten doet), en is het daarna geen B of C meer (en krijg je idd last van slicing zoals je al beschreef). Het verschil met pointers is dat je een A* terug kunt casten naar een B*, wat bij values van A en B natuurlijk niet kan (tenzij je daar uiteraard operators / ctors voor definieert, maar besef dan wel dat je met een kopie zit en niet meer met je originele B)

[ Voor 28% gewijzigd door .oisyn op 19-04-2008 13:33 ]

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


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 16-11 18:33
Het voorbeeld is wat dat betreft wat kort door de bocht, maar het geeft wel goed aan _waarom_ het niet kan.

Correct me if i'm wrong maar volgens mij is de (technische) reden daarachter dat sizeof( derived ) != altijd sizeof( base).

deze code bijvoorbeeld:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main( int arc, char* argv[] )
{
    vector<A> V;

    A a; a.a = 1;
    B b; b.a = 2;
    C c; c.a = 3;

    V.push_back( a );
    V.push_back( b );
    V.push_back( c );

    A a1 = V.front();
    //B b1 = *((B*)&V.front()+1);
    C c1 = *((C*)&V.front()+2);

    return 0;
}


Werkt voor een A en een C, maar niet voor een B. ( Een C is binairy compatible met een A, maar een B niet. )

[edit]
Aan 'binairy compatible' zit natuurlijk wel meer vast dan alleen een sizeof die even groot moet zijn.

[ Voor 7% gewijzigd door farlane op 19-04-2008 16:30 ]

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


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 08:45

.oisyn

Moderator Devschuur®

Demotivational Speaker

farlane schreef op zaterdag 19 april 2008 @ 16:13:
Correct me if i'm wrong maar volgens mij is de (technische) reden daarachter dat sizeof( derived ) != altijd sizeof( base).
Nee. Het is idd een praktische limitatie waardoor het überhaupt niet zou kunnen, maar het komt erop neer dat non-reference en non-pointer types nooit polymorph kunnen zijn. Als jij een A definieert, dan is dat altijd een A, en nooit stiekem een B of een C. De std::vector construct ook doodleuk een nieuwe A in de array, en gebruikt daarbij de copy ctor met als parameter het item dat jij meegeeft. Dat mag best een B zijn, hij wordt toch gecast naar een const A&
Werkt voor een A en een C, maar niet voor een B. ( Een C is binairy compatible met een A, maar een B niet. )

[edit]
Aan 'binairy compatible' zit natuurlijk wel meer vast dan alleen een sizeof die even groot moet zijn.
Zijn ze layout-compatible? Volgens mij gaat dat niet meer op als je subclassed namelijk, maar dat zou ik even op moeten zoeken. Desalniettemin heeft voor de kopie in de vector nooit de constructor voor C gedraaid, dus hij is dan sowieso niet goed geïnitialiseerd (ergo, het is gewoon geen C).

.edit:
9.2/14
Two POD-struct (clause 9) types are layout-compatible if they have the same number of nonstatic data members, and corresponding nonstatic data members (in order) have layout-compatible types (3.9).
A en C zijn dus niet layout-compatible, want ze hebben niet layout-compatible data members. Hoewel A ook een subobject is in C, is A geen data member van C. 10.1/3 zegt overigens nog dat de volgorde van de base classes in de layout van het derived type unspecified is.

[ Voor 16% gewijzigd door .oisyn op 19-04-2008 18:45 ]

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


  • citegrene
  • Registratie: Februari 2006
  • Laatst online: 02-11 03:00
Wel eens geprobeerd te defineren als A en te initialiseren als B?
Dus niet:
B b = B(); maar

A b = B();

Volgens mij kan je die dan in de vector stoppen als A en terug casten naar B?

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 08:45

.oisyn

Moderator Devschuur®

Demotivational Speaker

Waarom verkondig je die onzin terwijl er zojuist in een aantal posts duidelijk is uitgelegd dat dat niet zo is?

Maar laten we er voor de grap eens vanuit gaan dat jij gelijk hebt, en de A en B nemen zoals farlane die in z'n post had gedefinieerd. Waar wordt de array in B dan neergezet? Want A bestaat maar uit 1 int, terwijl B in totaal uit 1025 ints bestaat.

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


  • citegrene
  • Registratie: Februari 2006
  • Laatst online: 02-11 03:00
Je hebt gelijk, alhoewel ik de voorbeelden wel een beetje vreemd vind.
Aangezien er gewoon een B of C in A wordt gepropt.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 08:45

.oisyn

Moderator Devschuur®

Demotivational Speaker

Nee, er wordt een kopie gemaakt van het A subdeel van beide objecten. Door middel van de copy constructor van A, die een const A& verwacht. Een B of een C is uiteraard te casten naar een const A& en geven een referentie naar het A subdeel in die objecten. De copy constructor van A kopiëert de gegevens van de meegegeven A dus naar 'this'. Je propt dus geen B of C in A.

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

Pagina: 1