User Tools

Site Tools


cpp:polymorphism

Δυναμικός Πολυμορφισμός

Ας επανέλθουμε στο αρχικό παράδειγμα της κληρονομικότητας και ας ορίσουμε δύο νέες μεταβλητές α) μία μεταβλητή τύπου δείκτη και μία μεταβλητή τύπου αναφορά σε ένα αντικείμενο τύπου Shape ως εξής:

ShapeUsage.cpp
#include "Rectangle.cpp"
 
int main() {
  Shape shape(0x333333, 5);
  Shape &shape_ref = shape, *shape_ptr = &shape;
  Rectangle rectangle(0xffffff, 2, 10, 20);
  Shape &rect_ref = rectangle, *rect_ptr = &rectangle;
 
  cout << "Shape area: " << shape.getArea() << endl;
  cout << "Shape reference area: " << shape_ref.getArea() << endl;
  cout << "Shape pointer area: " << shape_ptr->getArea() << endl;
  cout << endl;
  cout << "Rectangle area: " << rectangle.getArea() << endl;
  cout << "Rectangle reference area: " << rect_ref.getArea() << endl;
  cout << "Rectangle pointer area: " << rect_ptr->getArea() << endl;
} 

Μεταγλωττίζοντας και εκτελώντας τον παραπάνω κώδικα λαμβάνουμε τα εξής:

Shape area: 0
Shape reference area: 0
Shape pointer area: 0

Rectangle area: 200
Rectangle reference area: 0
Rectangle pointer area: 0

Από τα παραπάνω συμπεραίνουμε ότι η επιλογή κλήσης της μεθόδου getArea δεν γίνεται δυναμικά με βάση τον τύπο του αντικειμένου στον οποίο δείχνει ο δείκτης ή η αναφορά, αλλά στατικά με βάση τον τύπο δεδομένων για τον οποίο δηλώνεται ο δείκτης ή η αναφορά. Σε αυτή την περίπτωση η επιλογή της μεθόδου γίνεται από τον compiler κατά τη μεταγλώττιση του προγράμματος.

Εάν θέλουμε η επιλογή της μεθόδου να γίνεται δυναμικά με βάση τον τύπο του αντικειμένου που δείχνει ο δείκτης ή η αναφορά θα πρέπει να δηλώσουμε τη μέθοδο getArea στη γονική κλάση Shape ως virtual όπως παρακάτω:

class Shape {
  public:
    virtual unsigned int getArea();
};
unsigned int Shape::getArea() { return 0; }

Με αυτό τον τρόπο δηλώνουμε προς τον compiler ότι η απόφαση για τον ποια μέθοδος θα κληθεί δεν θα ληφθεί κατά τη μεταγλώττιση, αλλά κατά την εκτέλεση του προγράμματος. Δηλώνοντας τη μέθοδο getArea ως virtual στη γονική κλάση το αποτέλεσμα της εκτέλεσης είναι το εξής:

Shape area: 0
Shape reference area: 0
Shape pointer area: 0

Rectangle area: 200
Rectangle reference area: 200
Rectangle pointer area: 200

Pure virtual συναρτήσεις και abstract κλάσεις

Εκτός από τις virtual μεθόδους που είδαμε προηγούμενα μπορούμε να έχουμε και pure virtual μεθόδους. Οι μέθοδοι που χαρακτηρίζονται pure virtual όταν ορίζονται σε μία κλάση, δηλώνεται μόνο το prototype τους, χωρίς να δηλώνεται σώμα και ακολουθεί η δήλωση =0.

virtual <return type> <function_name>(<function parameters) =0;

Η κλάση που περιέχει μία ή περισσότερες pure virtual συναρτήσεις είναι abstract και δεν μπορεί να παράγει αντικείμενα, ακόμη και εάν διαθέτει κατασκευαστή. Μόνο οι κλάσεις που θα κληρονομήσουν τη συγκεκριμένη κλάση και θα παρέχουν υλοποιήσεις όλων των pure virtual μεθόδων θα μπορέσουν να παράγουν αντικείμενα.

Για παράδειγμα, στην κλάση Shape είναι λογικό να δηλώσουμε την μέθοδο getArea ως pure virtual (αντί να επιστρέφει μηδέν) μιας και η συγκεκριμένη συνάρτηση δεν έχει νόημα για το κλάση Shape, αλλά μόνο για τις υποκλάσεις αυτής. Η κλάση Shape διαμορφώνεται ως εξής:

Shape.cpp
#include <iostream>
#include <string>
using namespace std;
 
#ifndef __SHAPE2D__
#define __SHAPE2D__
 
class Shape {
    unsigned int color;
  protected:
    unsigned char borderWidth;
  public:
    Shape(unsigned int c, unsigned char bw);
    Shape(unsigned char red, unsigned char blue, unsigned char green, unsigned char bw);
    void setColor(unsigned int c);
    void setColor(unsigned char red, unsigned char blue, unsigned char green);
    unsigned int getColor();
    unsigned char getBorderWidth();
    void setBorderWidth(unsigned char bw);
    virtual unsigned int getArea() = 0;
};
 
void Shape::setColor(unsigned int c) { color = c; }
void Shape::setColor(unsigned char red, unsigned char blue, unsigned char green) {
  color = red;
  color <<= 8;
  color |= blue;
  color <<= 8;
  color |= green;
}
 
unsigned int Shape::getColor() { return color; }
Shape::Shape(unsigned int c, unsigned char bw) : color(c), borderWidth(bw) {}
Shape::Shape(unsigned char red, unsigned char blue, unsigned char green, unsigned char bw) : borderWidth(bw) { setColor(red, blue, green); }
unsigned char Shape::getBorderWidth() { return borderWidth; }
void Shape::setBorderWidth(unsigned char bw) { borderWidth = bw; }
#endif

Αντίστοιχα, η μέθοδος getArea() της κλάσης Rectangle διαμορφώνεται ως εξής:

unsigned int Rectangle::getArea() {
  return width * height;
}

Επειδή η κλάση Shape είναι πλέον abstract δεν μπορεί να δώσει αντικείμενα. Έτσι η συνάρτηση main διαμορφώνεται ως εξής.

ShapeUsage.cpp
#include "Rectangle.cpp"
 
int main() {
  Rectangle rectangle(0xffffff, 2, 10, 20);
  Shape &rect_ref = rectangle, *rect_ptr = &rectangle;
 
  cout << "Rectangle area: " << rectangle.getArea() << endl;
  cout << "Rectangle reference area: " << rect_ref.getArea() << endl;
  cout << "Rectangle pointer area: " << rect_ptr->getArea() << endl;
} 
cpp/polymorphism.txt · Last modified: 2020/04/15 08:48 (external edit)