TI-S2

Virtual en abstract

Inhoud

Zie ook: Overerving

Superklassen en pointers

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?

UML print animal 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.

Generaliseren

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.

Virtual en abstract voor draw()

Om dit te bereiken moeten we twee C++ features gebruiken:

Virtual en abstract voor update()

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.

Virtual en abstract in UML

Schermobjecten met een gemeenschappelijk superklasse

Virtual en abstract in andere programmeertalen

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 en abstract in C++

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

Abstracte klassen

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?