Sida 1 av 1
Arv och överlagring i C++
Postat: 11 juli 2010, 11:34:23
av JJ
Som gammal C-programmerare tänkte jag att jag skulle ge C++ en chans. Antingen har jag gjort något fel eller missförstått någet eller möjligen är det en bugg i kompilatorn
Programmet simulerar djur. Varje djur vet vad som skall hända med det när det möter ett djur av annan typ. Lejonet vet att det äter när det träffar en zebra, zebran vet att den blir uppäten. Om ett djur inte vet vad det skall göra i mötet med ett annat djur så har det ett default-beteende. Ibatiba är ett okänt djur som har default-beteendet.
Problem 1: Raden
funkar inte (se nedan).
Problem2: Om man stoppar in två djur i vattenhålet så kan man se vad som händer när de två möts. Så var det tänkt iallafall. Men det fungerar inte så.
Någon som kan dethär och har lust att kolla på det?
Kod: Markera allt
// cpptst.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
class Animal {
public:
virtual void meet(Animal * p);
};
void Animal::meet(Animal * P)
{
std::cout << "Unknown behavior!" << std::endl; // default-beteendet
}
class Ibatiba:public Animal {
};
class Zebra;
class Lion:public Animal {
public:
void meet(Zebra * p);
};
void Lion::meet(Zebra * p)
{
std::cout << "I eat" << std::endl;
}
class Zebra:public Animal {
public:
void meet(Lion * p);
};
void Zebra::meet(Lion * p)
{
std::cout << "I am devored!" << std::endl;
}
// A water hole with room for two animals
class WaterHole {
public:
WaterHole(Animal * p1, Animal * p2);
void lets_meet(void);
private:
Animal * pa1;
Animal * pa2;
};
WaterHole::WaterHole(Animal * p1, Animal * p2)
{
pa1 = p1;
pa2 = p2;
}
void WaterHole::lets_meet(void)
{
pa1->meet(pa2);
pa2->meet(pa1);
}
int _tmain(int argc, _TCHAR* argv[])
{
Ibatiba * pIbatiba = new Ibatiba;
Lion * pLion = new Lion;
Zebra * pZebra = new Zebra;
std::cout << "A day in Africa:\n";
pLion->meet(pZebra);
pZebra->meet(pLion);
// Funkar inte, varför?
// Ibatiba ärver ju från Animal...
// cpptst.cpp(80): error C2664: 'Lion::meet' : cannot convert parameter 1 from 'Ibatiba *' to 'Zebra *'
//
// pLion->meet(pIbatiba);
std::cout << "At the water hole:\n";
WaterHole * pWaterHole = new WaterHole(pLion, pZebra);
pWaterHole->lets_meet();
return 0;
}
Såhär skulle jag vilja ha det:
A day in Africa:
I eat
I am devored!
Unknown behavior!
At the water hole:
I eat
I am devored!
Såhär blir det:
A day in Africa:
I eat
I am devored!
At the water hole:
Unknown behavior!
Unknown behavior!
Re: Arv och överlagring i C++
Postat: 11 juli 2010, 21:03:58
av SvenW
Frågan är välformulerad och förtjänar ett svar.
Tyvärr är jag varken C++ guru eller pedagog, och kan inte på ett vettigt sätt klara ut vad som händer men har kompletterat koden på ett par punkter.
Kanske kan det klargöra något. Se kommentarerna i koden.
Kod: Markera allt
#include <iostream>
class Animal {
public:
// virtual
void meet(Animal *p);
};
void Animal::meet(Animal * p)
{
std::cout << "Unknown behavior!" << std::endl; // default-beteendet
}
class Ibatiba:public Animal {
};
class Zebra;
class Lion:public Animal {
public:
using Animal::meet; // Synliggör Animal::meet , Hindrar "name-hiding"
void meet(Zebra *p);
};
void Lion::meet(Zebra *p)
{
std::cout << "I eat" << std::endl;
}
class Zebra:public Animal {
public:
void meet(Lion *p);
};
void Zebra::meet(Lion *p)
{
std::cout << "I am devored!" << std::endl;
}
// A water hole with room for two animals
class WaterHole {
public: // Man måste nog ta med alla alternativ, annars vore det kompilatortrolleri
WaterHole( Animal * p1, Animal * p2);
WaterHole( Lion *l1, Zebra *z2);
WaterHole( Zebra *z1, Lion *l2);
void lets_meet(void);
private:
Animal * pa1;
Animal * pa2;
Lion * pl1;
Lion * pl2;
Zebra * pz1;
Zebra * pz2;
};
WaterHole::WaterHole(Animal *p1, Animal *p2)
{
pa1 = p1;
pa2 = p2;
}
WaterHole::WaterHole(Lion *l1, Zebra *z2)
{
pl1 = l1;
pz2 = z2;
}
WaterHole::WaterHole(Zebra *z1, Lion *l2)
{
pz1 = z1;
pl2 = l2;
}
void WaterHole::lets_meet(void)
{
pa1->meet(pa2);
pa2->meet(pa1);
pz1->meet(pl1);
pz2->meet(pl1);
pl1->meet(pl2);
pl2->meet(pl2);
pl1->meet(pz2);
pl2->meet(pz2);
}
int main(int argc, char* argv[])
{
Animal * pAnimal = new Animal;
Animal * pIbatiba = new Ibatiba;
Lion * pLion = new Lion;
Zebra * pZebra = new Zebra;
std::cout << "A day in Africa:\n";
pLion->meet(pZebra);
pZebra->meet(pLion);
// Funkar inte, varför?
// Ibatiba ärver ju från Animal...
// cpptst.cpp(80): error C2664: 'Lion::meet' : cannot convert parameter 1 from 'Ibatiba *' to 'Zebra *'
// För svar se i 'class Lion:public Animal' ovan
//
//
pLion->meet(pAnimal);
pAnimal->meet(pAnimal);
pAnimal->meet(pLion);
pIbatiba->meet(pLion);
pLion->meet(pIbatiba);
pAnimal->meet(pLion);
pIbatiba->meet(pAnimal);
std::cout << "At the water hole:\n";
WaterHole * pWaterHole = new WaterHole(pLion, pZebra);
pWaterHole->lets_meet();
return 0;
}
Re: Arv och överlagring i C++
Postat: 11 juli 2010, 21:41:03
av JJ
Tack för att du tog dig tid att titta på mitt problem. Tyvärr var det alltså inte så enkelt att man kan klämma dit något i stil med "virtual" och så funkar allt
"Kompilatortrolleri"...jag trodde att "late binding" eller vad det heter kan ordna till det men jag kanske inte har greppat detta riktigt.
Jag är inte riktigt nöjd med din lösning. (Jag är tacksam förstås!) Jag kanske kunde förklarat det ordentligt men jag vill lätt kunna lägga till nya djur och då vill jag inte att vattenhålet skall behöva veta något om vilken typ av djur som finns där.
Exempel: Om vi tänker oss att vi släpper ut en tiger i Afrika så behöver tigern veta hur den skall bete sig inför varje djur och varje djur skall veta vad som händer med det i möte med den randiga katten. Vattenhålets funktion har inte ändrats. Dettta tycker jag borde kunna avspeglas i kode.
Det där med "name-hiding" var nytt för mig! (Jag fattade det inte riktigt men jag skall ta mig en funderare.
PS
Jag försvinner i några veckor!
Re: Arv och överlagring i C++
Postat: 11 juli 2010, 22:26:31
av swesysmgr
// Ibatiba ärver ju från Animal...
Fast du verkar ha typeat metoden meet på lejonet till zera, om metoden tar Animal istället som typ för invariabeln?
Jag minns inte om det gick att automatiskt uppcasta till Animal från Zebra eller Ibata eller om du får göra (Animal *) pZebra.
Re: Arv och överlagring i C++
Postat: 11 juli 2010, 23:48:42
av JJ
Tack ni som tittat på dethär!
Den lösning jag kommit fram till nu använder inte arv eller överlagring utan bara sådant som finns i C. Varje Animal kan tala om sin MeetClass. (enum MeetClass behöver heller inte mappa mot C++-klasserna man kan tex ha ett värde BIG_CAT som är gemensamt för lejon och tiger. Zebran blir ju uppäten i båda fallen.)
Kod: Markera allt
#include <iostream>
enum MeetClass {
ANIMAL,
LION,
ZEBRA,
};
class Animal {
public:
virtual void meet(Animal * p);
virtual enum MeetClass get_meet_class(void);
};
enum MeetClass Animal::get_meet_class(void)
{
return ANIMAL;
}
void Animal::meet(Animal * P)
{
std::cout << "Unknown behavior!" << std::endl; // default-behavior
}
class Ibatiba:public Animal {
};
class Zebra;
class Lion:public Animal {
public:
void meet(Animal * p);
enum MeetClass get_meet_class(void);
};
void Lion::meet(Animal * p)
{
enum MeetClass c;
c = p->get_meet_class();
switch(p->get_meet_class()) {
case ZEBRA:
std::cout << "I eat" << std::endl;
break;
case LION:
break;
default:
Animal::meet(p);
};
}
enum MeetClass Lion::get_meet_class(void)
{
return LION;
}
class Zebra:public Animal {
public:
void meet(Animal * p);
enum MeetClass get_meet_class(void);
};
enum MeetClass Zebra::get_meet_class(void)
{
return ZEBRA;
}
void Zebra::meet(Animal * p)
{
enum MeetClass c;
c = p->get_meet_class();
switch(p->get_meet_class()) {
case ZEBRA:
break;
case LION:
std::cout << "I am devored!" << std::endl;
break;
default:
Animal::meet(p);
};
}
// A water hole with room for two animals
class WaterHole {
public:
WaterHole(Animal * p1, Animal * p2);
void lets_meet(void);
private:
Animal * pa1;
Animal * pa2;
};
WaterHole::WaterHole(Animal * p1, Animal * p2)
{
pa1 = p1;
pa2 = p2;
}
void WaterHole::lets_meet(void)
{
pa1->meet(pa2);
pa2->meet(pa1);
}
int main()
{
Ibatiba * pIbatiba = new Ibatiba;
Lion * pLion = new Lion;
Zebra * pZebra = new Zebra;
std::cout << "A day in Africa:\n";
pLion->meet(pZebra);
pZebra->meet(pLion);
pLion->meet(pIbatiba);
std::cout << "At the water hole:\n";
WaterHole * pWaterHole = new WaterHole(pLion, pZebra);
pWaterHole->lets_meet();
return 0;
}
Re: Arv och överlagring i C++
Postat: 12 juli 2010, 01:39:11
av arvidb
Om du deklarerar en basklass och en ärvd klass:
Kod: Markera allt
#include <iostream>
using namespace std;
class Animal {
public:
void meet(Animal * p);
};
void Animal::meet(Animal *p)
{
cout << "class Animal meet" << endl;
}
class Lion : public Animal {
public:
void meet(Animal * p);
};
void Lion::meet(Animal *p)
{
cout << "class Lion meet" << endl;
}
så kommer datatypen class Lion att innehålla två metoder meet() (hela datatypen Animal plus det som tillkommer i Lion):
Kod: Markera allt
int main()
{
Lion *lion = new Lion;
Animal *animal;
lion->meet(NULL); // Anropar meet deklarerad i class Lion
animal = lion;
animal->meet(NULL); // Anropar meet deklarerad i class Animal
return (0);
}
ger
Kod: Markera allt
$ ./animals
class Lion meet
class Animal meet
Deklarerar du däremot Animals meet() virtual:
Kod: Markera allt
class Animal {
public:
void virtual meet(Animal *p);
};
så kommer Lions meet() att anropas istället om objektet instansierades som typen Lion, oavsett om du castar om objektet. Ovanstående ändring ger:
Dock är felet i ditt första exempel att du aldrig overridar Animals meet() - du deklarerar bara nya, annorlunda metoder i Lion och Zebra, med andra parametertyper (Lion * och Zebra * istf Animal *). lets_meet i WaterHole anropar alltså Animals meet(). För att få polymorfismen som du är ute efter måste du alltså göra som du visade i ditt senaste inlägg - deklarera metoderna i Lion och Zebra som meet(Animal *p) och titta på p i metoden för att avgöra beteende.
Att
inte fungerar beror på att C++ istf att overloada metoder
döljer metoder från basklassen som omdeklareras i ärvd klass med andra parametrar (oavsett om metoden i basklassen är virtual eller inte). Du kan skriva så här för att få tillbaka metoden:
Kod: Markera allt
class Lion : public Animal {
public:
using Animal::meet;
void meet(Zebra * p);
};
Inte direkt intuitivt, men C++ är verkligen inte ett intuitivt språk heller. IMO så har C++ en enda fördel mot C, och det är objektorienteringen. Iofs en extremt stor fördel... Tyvärr finns många nackdelar också som gör det till ett väldigt svårarbetat språk.
Här finns mer info om "dolda metoder".
Re: Arv och överlagring i C++
Postat: 12 juli 2010, 12:22:54
av kimmen
Jag kan rekommendera den där sidan arvidb länkade till. Det finns mycket matnyttigt där förutom just den där biten:
http://www.parashift.com/c++-faq-lite/
Re: Arv och överlagring i C++
Postat: 31 juli 2010, 17:24:37
av SvenW
Möjligen kan man testa med 'dynamic_cast', men det blir kanske lite klumpigt det också, eftersom alla kombinationer måste testas såvitt jag förstår
Rättelse: Det räcker nog att 'casta' argumenten. Anropen 'castas' nog med automatik om funktionen är virtuell!
Kod: Markera allt
void
WaterHole::lets_meet (void)
{
if (dynamic_cast < Lion * >(pa1))
if (dynamic_cast < Zebra * >(pa2))
{
cout << "dynamic_cast <Lion> pa1 <Zebra> pa2" << endl;
(dynamic_cast < Lion * >(pa1))->meet (dynamic_cast < Zebra * >(pa2));
(dynamic_cast < Zebra * >(pa2))->meet (dynamic_cast < Lion * >(pa1));
}
if (dynamic_cast < Zebra * >(pa1))
if (dynamic_cast < Lion * >(pa2))
{
cout << "dynamic_cast <Zebra> pa1 <Lion> (pa2)" << endl;
(dynamic_cast < Zebra * >(pa1))->meet (dynamic_cast < Lion * >(pa2));
(dynamic_cast < Lion * >(pa2))->meet (dynamic_cast < Zebra * >(pa1));
}
}
Re: Arv och överlagring i C++
Postat: 1 augusti 2010, 11:59:26
av mri
Intressant tråd det här.
'using' har jag aldrig använt på det sättet tidigare.
Halkade in på C++ FAQ LITE, och får (igen) konstatera hur omfattande C++ är, samt hur liten del jag egentligen behärskar.

Re: Arv och överlagring i C++
Postat: 8 augusti 2010, 11:09:37
av thebolt
Problemet i det här fallet är att du blandar arv och överlagring.. tänk på att _hela_ typen ingår i arvet, dvs även parametrarna, just som arvidb säger.
Det du försöker göra är något som normalt sett kallas för "double dispatch", dvs du vill anropa olika metoder beroende på _två_ typer, och är något som C++ inte stödjer i språket utan det finns bara "single dispatch" (vanliga virtuella funktioner). Man kan dock emulera det via t.ex. en visitor-pattern vilket finns exempel på på wikipedia
http://en.wikipedia.org/wiki/Visitor_pa ... 2B_Example
Re: Arv och överlagring i C++
Postat: 1 september 2010, 09:39:55
av JJ
Vill bara säga "Tack" för bra svar och intressanta länkar!
Nu förstår jag ju precis vad det vad jag gjorde för fel!