310 likes | 519 Views
Föreläsning 5. Problemet. Vi har sett att vi kan ersätta de metoder vi ärver från överklassen med egen funktionalitet (polymorfism).
E N D
Problemet • Vi har sett att vi kan ersätta de metoder vi ärver från överklassen med egen funktionalitet (polymorfism) class Bil : public Fordon{ Bil(Person & owner) : Fordon(owner) {}// Den nya versionen av GetPosition skall // returnera fordonets position förskjutet // 3 enheter i bägge axlar Point GetPosition() { return Point(m_position).Offset(3,3); }};
Problemet • Vad händer då en funktion som tar emot ”Fordon” får en ”Bil” som parameter? void fordonsinfo(Fordon & fordon){// vad händer om fordon är ett objekt av // klassen ”Bil”? // skriver vi ut positionen som ges av // ”Fordon::GetPosition()” eller // ”Bil::GetPosition()”? cout << fordon.GetPosition();};
Problemet – svar • Metoden ”Fordon::GetPosition” används • Detta beror på att allt funktionen fordonsinfo vet om är att det är ett fordon som har skickats som parameter. • Problemet orsakas av statisk bindning • Default-beteende i C++ • Vid kompileringstillfället bestäms det att ”Fordon::GetPosition()” skall användas
Problemet – önskat resultat • I de flesta fall vill man att objektets mest aktuella metod skall exekveras, inte en gammal metod från någon överklass • I vårat fall vill vi att ”Bil::GetPosition()” skall användas istället
Problemet – lösning • Om man istället vid exekveringstillfället (run-time) bestämmer vilken funktion som skall användas kan vi få önskat resultat • Dynamisk bindning • Vi vill använda oss av metoduppslagning
Problemet – lösning forts. • C++ erbjuder en lösning för dynamisk bindning av metoder • Nyckelordet ”virtual” framför en metod säger åt C++ att metoduppslagning skall användas för denna specifika metod • ”virtual” metoder ”ärvs”, dvs är en metod virtuell i överklassen så är den virtuell i underklasserna
Exempel (virtuell metod) class Fordon{public: Fordon() : m_position(0,0) {}virtual Point GetPosition() { return m_position; }protected: Point m_position;}; class Bil : public Fordon{public: Bil() {} // Inget anrop av Fordon() behövsvirtual Point GetPosition() { return Point(m_position).Offset(3,3); }};
Problemet – följder • Dynamisk bindning är inte lika effektivt som statisk bindning • stor anledning till att statisk bindning är default i C++ • Dagens processorer hanterar dynamisk bindning mycket effektivt • Inte längre lika stor nackdel
Husdjur class Pet{public:virtual void mess_up() { // Släpp hår överallt }protected: Color m_color;};
Kanin class Rabbit : public Pet{public: // vi ärver alla metoder från Petprotected: Carrot m_favoriteCarrot;};
Hund class Dog : public Pet{public:void eat_cat();virtualvoid mess_up() {// Bit sönder saker // Kräks på nya mattan }protected: Bone m_favoriteBone;};
Katt class Cat : public Pet{public: void scratch_sofa(); virtual void mess_up() {// släpa in möss // gör dessutom som alla andra Pet::mess_up(); }protected:};
Pure virtual • Antag att man inte kan bestämma beteende för en virtuell funktion i en överklass • Exempelvis: Pet::mess_up() har ingen bestämd definition. • Metoden kan då deklareras som rent virtuell (pure virtual) • mess_up() = 0; • Tvingar alla underklasser att implementera metoden mess_up()
Exempel (pure virtual) • Vi finner inget passande standardbeteende för när husdjur stökar till • mess_up() blir en pure virtual metod class Pet{public:virtual void mess_up() = 0;protected: Color m_color;};
Exempel (pure virtual) forts. • Vilket även innebär att alla underklasser måste implementera mess_up() class Rabbit : public Pet{public:virtual void mess_up() { // Släpp hår överallt }protected: Carrot m_favoriteCarrot;};
Abstrakt överklass • Klass som innehåller en/flera rent virtuella metoder • Klasser med rent virtuella metoder kan ej instansieras • Exempel: Husdjur, hur skulle detta objekt se ut? • Underklass måste komplettera klassen för att kunna instansieras • Referenser/pekare till objekt av överklassen kan ändå anropa de rent virtuella metoderna
Rent abstrakt överklass • Alla medlemmar är rent virtuella (pure virtual) • Definierar ett gränssnitt som arvtagare måste implementera (för att kunna instansieras)
Virtuella destruktorer • När man använder virtuella metoder i en klass bör man ta som vana att även deklarera destruktorn virtuell. • Om man misstänker att klassen skall användas polymorft bör man även skapa en virtuell destruktor • Att inte skapa en virtuell destruktor kan i vissa fall få förödande konsekvenser
Lillhjärna class Cerebellum // Lillhjärnan{public: ~Cerebellum() { cout << ”Signing off”; }virtual void react() { fight(); eat(); sleep(); reproduce(); }void fight();void sleep();void eat();void reproduce();};
Storhjärna class Cerebrum : public Cerebellum // Storhjärnan{public: Cerebrum() { m_mem = new Memory(); } ~Cerebrum() { delete m_mem; }virtual void react() {switch(think()) {case 0: eat(); break;case 1: sleep(); break;case 2: reproduce(); break; } }int think(); // return appropriate actionprotected: Memory *m_mem;};
Destruktorhaveri void main(){ Cerebellum * lill = new Cerebellum(); Cerebrum * stor = new Cerebrum(); squash(lill); // anropar lill-destruktor squash(stor); // anropar lill-destruktor// missar alltså stor-destruktorn pga att // destruktorn inte är virtual } void squash(Cerebellum * brain){// statisk bindning (~Cerebellum)delete brain;}
Lösning • Lösningen på problemet med felaktigt destruktoranrop är givetvis att göra destruktorn virtual
Fordon Bil Båt Amfibiebil Virtuellt arv • När man ärver multipelt från två eller fler klasser med gemensamma överklasser uppstår datadubblering • För att undvika detta kan man ärva klasser virtuellt, vilket gör att alla klasser i arvshierarkin endast finns med i en uppsättning
Exempel (virtuellt arv) class Fordon{public: Fordon(Person & owner); Point GetPosition() { return m_position; } Person & GetOwner() { return m_owner; }protected: Point m_position; Person & m_owner;};
Exempel (virtuellt arv) class Bil : publicvirtual Fordon {public: Bil(int age) { m_age = age; }void Print() { cout << ”Bil, årsmodell ” << m_model; }protected:int m_model;}; class Bat : publicvirtual Fordon{public: Bat(int length) { m_length = length; }void Print() { cout << ”Båt, längd ” << m_length; }protected:int m_length;};
Exempel (multipelt arv) • Klassen ärver multipelt precis som tidigare (får nu en uppsättning fordonsegenskaper) class Amfibiebil : public Bil, public Bat{public: Amfibiebil(int age, int length) : Bil(age), Bat(length) {}// för att undvika namnkonflikter måste vi // ersätta metoden Printvoid Print() { Bil::Print(); Bat::Print(); }};
Sammanfattning – virtuellt arv • Virtuellt arv måste specificeras redan på den nivån i klasshierarkin där de ärver från samma klass. • Dubblering av data kan undvikas genom att ärva ”virtual” • Varje datamedlemsaccess sker virtuellt • Prestandaförsämring, värre än virtuella funktioner
Interface-klasser • Klasser utan datamedlemmar • Mestadels rent virtuella metoder • Kan ha enklare virtuella metoder med implementation • Undviker problem vid multipelt arv eftersom ingen data kan dubbleras • Ärv maximalt en klass med implementation, resten Interface-klasser • Namnkollisionsproblematiken kvarstår dock
Sammanfattning • Polymorf metod (polymorfism) • ”metod med många former” • Egenskapen att kunna ha flera olika beteenden beroende på objekt • Ersättning av metoder i underklasser • Statisk bindning • Kompilatorn bestämmer vid kompileringstillfället vilken metod som skall anropas
Sammanfattning forts. • Dynamisk bindning • Programmet avgör vilken metod som skall anropas först när programmet kör (run-time) • Åstadkoms i C++ genom ”virtual” • Inte lika effektivt som statisk bindning, men inte så allvarligt på dagens processorer