User Tools

Site Tools


cpp:multiple_inheritance

Κληρονομικότητα πολλών γονικών κλάσεων

Η C++ δίνει την δυνατότητα να έχετε περισσότερες από μία γονικές κλάσεις για μία κλάση απόγονο. Εκ πρώτης όψεως αυτό έρχεται σε αντίθεση με τη βασική αρχή στη Java που επιτρέπει μόνο μία γονική κλάση κατά την διαδικασία της κληρονομικότητας. Γενικά, η πολλαπλή κληρονομικότητα θα πρέπει να χρησιμοποιείται με πολύ προσοχή και όπου είναι δυνατόν να αποφεύγεται.

Δεν πρέπει όμως να ξεχνάμε ότι τα interfaces στη C+ ορίζονται μέσω abstract κλάσεων. Έτσι εάν θέλετε μία κλάση να υλοποιεί ένα interface και παράλληλα να είναι απόγονος άλλης κλάσης τότε δεν έχετε παρά να χρησιμοποιήσετε πολλαπλή κληρονομικότητα. Ας υποθέσουμε ότι έχετε την κλάση LinkedStack η οποία υλοποιεί το interface Stack και παράλληλα χρησιμοποιεί μία συνδεδεμένη λίστα για να υλοποιήσει τη επιθυμητή λειτουργικότητα της στοίβας. Ας υποθέσουμε ότι η συγκεκριμένη κλάση επεκτείνει την κλάση LinkedList. Σε αυτή την περίπτωση η κλάση LinkedStack θα κληρονομεί δύο κλάσεις α) την abstract κλάση Stack και β) την κλάση LinkedList. Δείτε τον παρακάτω κώδικα που αποτελεί υλοποιεί τον σχήμα που περιγράψαμε.

Stack.h
#include <iostream>
using namespace std;
 
#ifndef __STACK_H__
#define __STACK_H__
class Stack {
  public:
    virtual int size()=0;
    virtual void push(int o)=0;
    virtual int pop()=0;
    virtual int top()=0;
};
#endif
LinkedList.h
#include <iostream>
using namespace std;
 
#ifndef __LINKEDLIST_H__
#define __LINKEDLIST_H__
 
class LinkedNode {
  int element;
  LinkedNode *next;
public: 
  LinkedNode(LinkedNode *nxt, int e); 
  LinkedNode(int e); 
  int getElement();
  LinkedNode* getNext();
  void setElement(int e);
  void setNext(LinkedNode *nxt);
};
 
class LinkedList {
  LinkedNode *head;
  int listSize;
public:
  LinkedList();
  int size();
  void insertFirst(int o);
  void insertAfter(LinkedNode *prev, int o);
  int deleteFirst();
  int deleteAfter(LinkedNode *prev);
  int valueAt(int pos);
};
 
#endif
LinkedList.cpp
#include "LinkedList.h"
 
/* LinkedNode functions 
 * */
 
LinkedNode::LinkedNode(LinkedNode *nxt, int e) {
  next = nxt;
  element = e;
}
 
LinkedNode::LinkedNode(int e) : LinkedNode(NULL,e) { } 
int LinkedNode::getElement() { return element; }
LinkedNode* LinkedNode::getNext() { return next; } 
void LinkedNode::setElement(int  e) { element = e; }
void LinkedNode::setNext(LinkedNode *nxt) { next = nxt; }
 
/* LinkedList functions 
 * */
 
LinkedList::LinkedList() {
  listSize = 0;
  head = NULL;
}
 
int LinkedList::size() { return listSize; }
 
void LinkedList::insertFirst(int o) {
  LinkedNode *node = new (nothrow) LinkedNode(head, o);
  head = node;
  listSize++;
}
 
void LinkedList::insertAfter(LinkedNode *prev, int o) {
  LinkedNode *node = new (nothrow) LinkedNode(prev->getNext(), o);
  prev->setNext(node);
  listSize++;  
}
 
int LinkedList::deleteFirst() {
  if(size()==0)
    return -1;
  LinkedNode *node = head;
  head = head->getNext();
  int o = node->getElement();
  delete node;
  listSize--;
  return o;
}
 
int LinkedList::deleteAfter(LinkedNode *prev) {
  LinkedNode *node = prev->getNext();
  prev->setNext(node->getNext());
  int o = node->getElement();
  delete node;
  listSize--;
  return o;
}
 
int LinkedList::valueAt(int pos) {
  if(pos >= size() )
    return -1;
  LinkedNode *curr = head;
  for(int i=0; i<pos; i++, curr=curr->getNext() )
    ;
  return curr->getElement();
 
}
LinkedStack.h
#include <iostream>
using namespace std;
 
#include "LinkedList.h"
#include "Stack.h"
 
#ifndef __LINKEDSTACK_H__
#define __LINKEDSTACK_H__
 
class LinkedStack : public Stack, private LinkedList { 
public:
  LinkedStack();
  int size();
  void push(int o);
  int pop();
  int top();
};
#endif
LinkedStack.cpp
#include "LinkedStack.h"
 
LinkedStack::LinkedStack() {}
int LinkedStack::size() { return LinkedList::size(); }
 
void LinkedStack::push(int o) {
  insertFirst(o);
}
 
int LinkedStack::pop() {
  return deleteFirst();
}
 
int LinkedStack::top() {
  return valueAt(0);
}
StackUsage.cpp
#include "LinkedStack.h"
 
#define ARRAY_SIZE 10
 
void invertArray(int array[], int arraySize, Stack &stack) {
  for(int i=0; i<arraySize; i++)
    stack.push(array[i]);
 
  for(int i=0; i<arraySize; i++)
    array[i] = stack.pop();
}
 
int main() {
  LinkedStack st;
  int array[] = { 100, 99, 98, 97, 96, 95, 94, 93, 92, 91 };  
 
  for(int e:array)
    cout << e << endl;
  cout << endl;
 
  invertArray(array, ARRAY_SIZE, st);
 
  for(int e:array)
    cout << e << endl;
}

Ο κώδικας μεταγλωττίζεται ως εξής:

g++ -g -std=c++11 -o stack StackUsage.cpp ArrayStack.cpp LinkedList.cpp LinkedStack.cpp

Σε αυτό το σημείο θα πρέπει να σημειώσουμε ότι η επιλογή της κληρονομικότητας στη συγκεκριμένη περίπτωση είναι λανθασμένη και έγινε μόνο και μόνο για λόγους παρουσίασης της πολλαπλής κληρονομικότητας.

Όπως έχουμε εξηγήσει και σε προηγούμενες ενότητες το κριτήριο για να χρησιμοποιήσουμε κληρονομικότητα ή όχι είναι εάν ο νέος τύπος που προκύπτει από τη σχέση κληρονομικότητα διατηρεί όλα τα χαρακτηριστικά του γονικού τύπου. Στο συγκεκριμένο παράδειγμα, το ερώτημα που πρέπει να τεθεί είναι εάν η κλάση LinkedStack είναι και LinkedList. Η απάντηση είναι αρνητική, πράγμα που οδηγεί στο συμπέρασμα ότι η επιλογή τη κληρονομικότητας είναι λανθασμένη. Σωστότερη επιλογή θα ήταν η κλάση LinkedList να αποτελεί πεδίο της κλάσης LinkedStack.

Προκειμένου να μην δημοσιοποιήσουμε την παραπάνω λάθος σχέση κληρονομικόητας, ορίζουμε τη σχέση κληρονομικότητας της κλάσης LinkedStack σε σχέση με την κλάση LinkedList ως private. Σε αυτή την περίπτωση τα public και protected μέλη της κλάσης LinkedList γίνονται private στην κλάση LinkedStack. Έτσι οι κλάσεις ή οι συναρτήσεις που χρησιμοποιούν τη LinkedStack είναι αδύνατο να γνωρίζουν ότι η συγκεκριμένη κλάση είναι απόγονος της LinkedList. Επίσης είναι αδύνατο να χρησιμοποιήσουν κάποια από τις μεθόδους της LinkedList μέσω της LinkedStack.

Εάν η σχέση κληρονομικότητας είχε οριστεί public, τότε οι μέθοδοι και οι κλάσεις που χρησιμοποιούν τη LinkedStack θα γνωρίζουν τη σχέση κληρονομικότητας και θα μπορούν να χρησιμοποιήσουν τα public μέλη της κλάσης LinkedList. Επίσης, οι απόγονοι θα μπορούν να χρησιμοποιήσουν τα public και protected μέλη της κλάσης LinkedList.

Αντίστοιχα, εάν η σχέση κληρονομικότητας είχε οριστεί protected, τότε οι απόγονοι της LinkedStack θα γνωρίζουν τη σχέση κληρονομικότητας και θα μπορούν να χρησιμοποιήσουν τα public και protected μέλη της κλάσης LinkedList.

cpp/multiple_inheritance.txt · Last modified: 2021/05/07 08:44 (external edit)