14. Йерархии на наследственост

План:
Абстрактни класове
Получаване на информация по време на изпълнение
Многократна наследственост

Абстрактни класове (преговор)

Пример: Йерархия на геометрични фигури в равнината.

- Ако искаме да имаме полиморфна функция за лице на фигура - area, то тази функция трябва да бъде дефинирана в базовия клас Shape.
- Тази функция има смисъл за всеки клас, производен на класа Shape, но в класа Shape тя няма смисъл.
- Такава функция се декларира по специален начин в базовия клас и се нарича чисто виртуална член-функция.

class Shape {
...
virtual double area() const = 0;
};

- Клас, който има поне една чисто виртуална член-функция се нарича абстрактен клас.
- Клас, който се състои изцяло от чисто виртуални член-функции понякога се нарича интерфейс.

Пример: Абстрактен клас Shape и производни класове Triangle и Circle.

class Shape {
public:

    virtual double perimeter() const = 0;
    virtual double area() const = 0;
};

class Triangle: public Shape {
public:
    ...

    virtual double perimeter() const
    { return a + b + c; }
    virtual double area() const
    {   double p = (a + b + c)/2;
        return sqrt(p*(p-a)*(p-b)*(p-c));
    }
private:
    double a, b, c;
};

class Circle: public Shape {
public:
    ...
    virtual double perimeter() const
    { return 2*PI*r; }
    virtual double area() const
    { return PI*r*r; }
private:
    double r;
};
...
int main()
{
//  Shape* s = new Shape;               // ERROR!!
    Shape* s = new Triangle(4, 5, 6);   // First *s is a triangle

    cout << "Area is " << s->area() << "\n";   
    s = new Circle(10);                 // Now *s is a circle
    cout << "Area is " << s->area() << "\n";
    ...
    return 0;
}
abstract.cpp

Interfaces in C++ (Abstract Classes)

Получаване на информация по време на изпълнение

* Операция dynamic_cast
Унарната операция dynamic_cast променя типа на указател.
Тя изисква тип като параметър на шаблон, и аргумент, който трябва да бъде указател или псевдоним (референция).

Пример:  Manager е производен клас на Employee с виртуална функция:

    // Implicitly converts pointer e from Manager* to Employee*
    Employee* e = new Manager("Sarah", 67000, "A");

    // Explicitly converts pointer e from Employee* to pointer m from Manager*
    Manager* m = dynamic_cast<Manager*>(e);
    if (m != NULL) cout << m->get_name() << endl << m->get_department() << endl;


Ако аргументът е указател сочещ към клас, който е различен от параметъра на шаблона, резултатът от операцията е NULL.

    Employee* e = new Employe("Sarah", 67000);

    // Explicitly converts pointer e from Employee* to pointer m from Manager*
    Manager* m = dynamic_cast<Manager*>(e);
    if (m == NULL) cout << "NULL" << endl;

dcast.cpp

Пример: Проверка дали указател сочи към обект от даден клас в полиморфна колекция.

     vector<Employee*> department;
    .......
    for (int i = 0; i < department.size(); i++)

    {
        Manager* m = dynamic_cast<Manager*>(department[i]);
        if (m != NULL)
        {
            cout << "Employee " << department[i]->get_name()
                    << " is a manager.\n";
            m->set_bonus(2000); // Can now invoke manager member functions
        }
        else
            cout << "Employee " << department[i]->get_name()
                    << " is not a manager.\n";
    }
dynamic.cpp

Повече за dynamic_cast.


* Операция typeid

За да се получи (името на) конкретен тип на даден обект,  се използва операция typeid.
Аргумент на операцията е израз, или име на клас и връща обект от тип type_info, който е дефиниран в заглавния файл <typeinfo>.
В класа type_info има дефинирана член-функция name(), която връща име на тип.

Пример:
for (int i = 0; i < department.size(); i++)
        cout << typeid(*department[i]).name() << "\n";

Друг начин за тестване на типа на обект е да се сравнява стойността в typeinfo с тази на известен клас:

 for (int i = 0; i < department.size(); i++)
    {
        if (typeid(*department[i]) == typeid(Manager))
            cout << "Employee " << department[i]->get_name()
            << " is a manager. \n";
        else
            cout << "Employee " << department[i]->get_name()
            << " is not a manager. \n";
    }

dynamic.cpp

typeid operator

Използването на dynamic_cast  и typeid трябва да се избягва, като се замества с механизма на виртуалните функции.

Многократна наследственост (множествено наследяване)

Пример: Потоковата библиотека на С++ се състои от няколко класове, свързани в следната йерархия:
 

Когато се използва многократно наследяване, диаграмата за наследяване на класове вече не е дърво, а насочен ацикличен граф или DAG.

Пример:

Викане на функция в производен клас, която е дефинирана и в двата базови класове.



// multi.cpp
#include<iostream>
using namespace std;

class Student {
public:
    Student()
    {  id = "sid"; }
    virtual string get_id() const
    { return id; }
private:
    string id;
};

class Employee {
public:
    Employee()
    { id = "eid"; }
    virtual string get_id() const
    { return id; }
private:
    string id;
};

class TeachingAssistant : public Employee, public Student {
public:
    TeachingAssistant():Employee(), Student(){}
//    string get_id() const;
    string student_id() const;
};

string TeachingAssistant::student_id() const
// Make student value available by a different name
{
    return Student::get_id();
}

int main()
{
    TeachingAssistant* fred = new TeachingAssistant();
    Employee* new_hire = fred;    // Legal, because a TeachingAssistant is-a Employee
    Student* advisee = fred;      // Legal, because a TeachingAssistant is-a Student

    cout << "Your number is " << fred->get_id() << "\n";  // Error, ambiguous member function name
   
    Student* mary = new Student();
 //   Student* mary = new TeachingAssistant();

    TeachingAssistant* lab_instructor = dynamic_cast<TeachingAssistant*>(mary);
    if (lab_instructor != NULL)
        cout << "Yes, mary is a TeachingAssistant. \n";
    else
        cout << "No, mary is not a TeachingAssistant. \n";
   
  
    return 0;
}

Решение е да си използва пълното име на функцията или да се предефинира тази функция в производния клас.

 class TeachingAssistant : public Student, public Employee
  {
  public:
     string get_id() const;
     string student_id() const;
  };
// get_id will return Employee identification number string TeachingAssistant::get_id()
{
     return Employee::get_id();
  }
string TeachingAssistant::student_id()
// Make student value available by a different name
  {
     return Student::get_id();
  }
Multiple Inheritance in C++


* Дублиране на базови класове.

class MultiplePartTime : public Employee, public Employee // Error
{ ... };

При използване на множествено наследяване може да се появи дублиране на базов клас.

Пример: Student и Teacher са производни на Person, а TeachingAssistant е производен на Student и Teacher.

class Person  {
 public:
       Person(string n);
       string get_name() const;
 private:
       string name;
 };
class Student : public Person {
... };
class Employee : public Person {
... }; Cay S. Horstmann, Timothy A. Budd Big C++ 2008
class TeachingAssistant : public Employee, public Student {
... };

Във всеки обект от клас TeachingAssistant  се съдържат два пъти данни от клас Person.

За да имаме само един екземпляр от клас  Person, използваме виртуално наследяване.

class Student : virtual public Person {
... };
class Employee : virtual public Person {
... };
class TeachingAssistant : public Student, public Employee {
... };


Cay S. Horstmann, Timothy A. Budd Big C++ 2008
double.cpp

Solving the Diamond Problem with Virtual Inheritance

Обща препоръка е да се избягва многократна наследственост.