Zie ook: Overerving
Als je in C++ een object van een afgeleide klasse gebruikt via een pointer of reference naar zijn superklasse, dan kan je alleen bij de eigenschappen van de superklasse. Het subklasse deel is er nog wel, maar het is voor jouw code onzichtbaar. Dit gaat zo ver dat als je in de superklasse en de subklasse een functie hebt met dezelfde naam, je dan toch de superklasse functie gebruikt.
class animal {
public:
void print(){
std::cout <<"I am an animal.\n";
}
};
class duck : public animal {
public:
void print(){
std::cout <<"I am a duck.\n";
}
};
animal a;
duck d;
a.print(); // I am an animal
d.print(); // I am a duck
animal & ar = d;
ar.print(); // I am an animal <======== !!
void f( animal & x ){
x.print();
}
f( a ); // I am an animal
f( d ); // I am an animal <======== !!
Codevoorbeeld 12-06 - Welke functie wordt aangeroepen?
Het gebruiken van de superklasse functie is niet altijd gewenst: het is bijvoorbeeld handig als een verzameling objecten (bijvoorbeeld figuren die we op het scherm afbeelden) allemaal eenzelfde functie hebben die we kunnen aanroepen, maar dat wat ze dan in die functie doen verschillend is.
Naast de bewegende bal zouden we ook muren op het scherm willen afbeelden, die niet bewegen, maar op een andere manier reageren op het verstrijken van tijd. Vervolgens willen we deze schermobjecten onthouden in een array, zodat we ze in een loop kunnen bijwerken en afbeelden.
Om dit te kunnen doen gebruiken we weer inheritance, maar wat we doen is het omgekeerde van specialiseren: we moeten generaliseren. De eigenschappen die de verschillende schermobjecten gemeenschappelijk hebben brengen we onder in een gemeenschappelijk superklasse (‘common base class’):
Deze superklasse kunnen we noemen naar wat zijn objecten zijn (schermobjecten?), of zoals in dit geval, naar wat je er mee kunt doen (je kunt ze tekenen). Anders dan bij de bal die erft van een cirkel, is de superklasse nu iets volkomen theoretisch: een drawable zelf is niets, het is abstract: het is alleen het gemeenschappelijke deel van zijn concrete subklassen.
draw()
zou moeten zijn:Om dit te bereiken moeten we twee C++ features gebruiken:
draw
functie is virtual.
Dit betekent dat als we deze functie aanroepen, we de functie van het ‘echte’ object aanroepen. Dit is iets langzamer en kost iets meer geheugen dan het aanroepen van een gewone functie, vandaar dat het in C++ niet de default is.draw()
functie is behalve virtual ook nog abstract.
Dit betekent dat er in drawable
helemaal geen (drawable-specifieke) draw()
functie is. Dat is ook wel logisch, hoe zou je een object willen tekenen als je niet weet wat voor object het is?De update()
functie is wel virtual, want als je een object (waar je verder niets van weet, alleen dat het een object is) wil bijwerken dan moet je de update
functie van dat eigenlijke object aanroepen. Maar bijwerken is voor veel objecten (bijvoorbeeld de gewone lijn, cirkel en rechthoek) een lege operatie. Daarom is update() niet abstract: er is een (lege) implementatie gegeven. De ‘gewone’ sub objecten hoeven die dus niet te herdefiniëren. De bal doet dat wel, want die moet wel bewegen.
In talen die meer op gemak voor de programmeur en minder op snelheid gefocust zijn wordt dit onderscheid doorgaans niet gemaakt en zijn alle klasse functies (methoden) virtual
. Dit geldt bijvoorbeeld voor Python en Java.
virtual
aangegeven met het virtual
keyword voor de functie, enabstract
met = 0
op de plaats van de functie body.‘override’
achter de parameter lijst.class drawable {
protected:
window & w;
vector location;
public:
drawable( window & w, const vector & location );
virtual void draw() = 0;
virtual void update(){};
};
class circle : public drawable {
protected:
int radius;
public:
circle( window & w, const vector & midpoint, int radius );
void draw() override;
};
class ball : public circle {
private:
vector speed;
public:
ball( window & w, const vector & midpoint, int radius, const vector & speed );
void update() override;
};
Codevoorbeeld 12-07 - C++: drawable, circle, ball
Als een klasse een of meer abstracte functies heeft (in het besproken geval geldt dit dus alleen voor drawable) dan is die klasse als geheel abstract.
class abstract {
public:
virtual void draw() = 0;
};
abstract obj; // ERROR: object is abstract
obj.draw(); // and what would this call anyway?
class concrete : public abstract {
public:
void draw() override {}
};
concrete brick;
brick.draw(); // call the concrete draw()
abstract & p = brick;
p->draw(); // again, call the concrete draw
Codevoorbeeld 12-08 - Een variabele van een abstracte klasse?