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

#include <iostream>
#include <cmath>
using namespace std;

class Shape {
public:
    virtual double perimeter() const = 0;
    virtual double area() const = 0;
};

class Triangle: public Shape {
public:
    Triangle(double aa, double bb, double cc):a(aa), b(bb), c(cc) {}
    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;
};

const double PI = 3.14;

class Circle: public Shape {
public:
    Circle(double rr):r(rr) {}
    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 << "Perimeter is " << s->perimeter() << endl; cout << "Area is " << s->area() << "\n";

 s = new Circle(10); // Now it is a circle cout << "Perimeter is " << s->perimeter() << endl; cout << "Area is " << s->area() << "\n";

 return 0;
 }
nkirov@cpp % c++ abstract.cpp
nkirov@cpp % ./a.out
Perimeter is 15
Area is 9.92157
Perimeter is 62.8
Area is 314

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
#include <iostream>
#include <string>

using namespace std;

class Employee {
public:
    Employee(string emp_name, double init_salary);
    void set_salary(double new_salary);
    string get_name() const;
    double get_salary() const;
    virtual void print() const;
private:
    string name;
    double salary;
};

class Manager : public Employee {
public:
    Manager(string n, double sal, string dept);
    string get_department() const;
    void print() const;
private:
    string department;
};

int main()
{
    Employee* e = new Manager("Sarah", 67000, "ABV");
// Explicitly converts pointer e from Employee* to pointer m from Manager*
    Manager* m = dynamic_cast<Manager*>(e);
    e->print();
    if (m != NULL) m->print();
   
    e = new Employee("Smith", 65000);
    m = dynamic_cast<Manager*>(e);
    if (m == NULL) cout << "NULL" << endl;

    return 0;
}
nkirov@cpp % c++ dcast.cpp
nkirov@cpp % ./a.out
Sarah 67000
ABV
Sarah 67000
ABV
NULL

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

     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

#include <iostream>
#include <vector>
#include <typeinfo>
using namespace std;

class Employee {
public:
    Employee(string, double);
    void set_salary(double);
    string get_name() const;
    double get_salary() const;
    virtual void print() const;
 //   virtual void set_bonus(double) = 0;
private:
    string name;
    double salary;
};

class Manager : public Employee {
public:
    Manager(string n, double sal, string dept);
    string get_department() const;
    virtual void print() const;
    void set_bonus(double b)
    {
        set_salary(get_salary() + b);
    }
private:
    string department;
};

    Employee::Employee(string emp_name, double init_salary)
    {
        name = emp_name;
        salary = init_salary;
    }
   
    void Employee::set_salary(double new_salary)
    {
        salary = new_salary;
    }
   
    string Employee::get_name() const
    {
        return name;
    }
   
    double Employee::get_salary() const
    {
        return salary;
    }
   
    void Employee::print() const
    {
        cout << get_name() << " " << salary << endl;
    }
   
    Manager::Manager(string n, double sal, string dept)
    : Employee(n, sal)
    {
        department = dept;
    }
   
    string Manager::get_department() const
    {
        return department;
    }
   
    void  Manager::print() const
    {
        Employee::print();
        cout << department << endl;
    }

void test_dynamic_cast(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";
    }
}

void test_typeid(vector<Employee*> department)
{
    for (int i = 0; i < department.size(); i++)
        cout << typeid(*department[i]).name() << "\n";
   
    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";
    }
}

int main()
{
    // Implicitly converts from Manager to Employee
    Employee* e = new Manager("Sarah", 67000, "A");
    cout << "1. "; e->print();
    // Explicitly converts from Employee to Manager
    Manager* m = dynamic_cast<Manager*>(e);
     cout << "2. "; m->print();
   
  //  m = e; // ERROR!
    e = m;
     cout << "3. "; e->print();
    m = static_cast<Manager*>(e);
     cout << "4. "; m->print();
    e = static_cast<Employee*>(m);
     cout << "5. "; e->print();
   
    vector<Employee*> department;
    department.push_back(e);
    department.push_back(new Employee("Harry", 30000));
    department.push_back(new Manager("John", 60000, "B"));
    cout << "test_dynamic_cast" << endl;
    test_dynamic_cast(department);
    cout << "test_typeid" << endl;
    test_typeid(department);
   
    return 0;
}

nkirov@cpp % c++ dynamic.cpp
dynamic.cpp:96:24: warning: expression with side effects will be evaluated
      despite being used as an operand to 'typeid'
      [-Wpotentially-evaluated-expression]
        cout << typeid(*department[i]).name() << "\n";
                       ^
dynamic.cpp:100:20: warning: expression with side effects will be evaluated
      despite being used as an operand to 'typeid'
      [-Wpotentially-evaluated-expression]
        if (typeid(*department[i]) == typeid(Manager))
                   ^
2 warnings generated.
nkirov@cpp % ./a.out
1. Sarah 67000
A
2. Sarah 67000
A
3. Sarah 67000
A
4. Sarah 67000
A
5. Sarah 67000
A
test_dynamic_cast
Employee Sarah is a manager.
Employee Harry is not a manager.
Employee John is a manager.
test_typeid
7Manager
8Employee
7Manager
Employee Sarah is a manager.
Employee Harry is not a manager.
Employee John is a manager.

Повече за 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;
}
nkirov@cpp % c++ multi.cpp
multi.cpp:43:41: error: member 'get_id' found in multiple base classes of
      different types
     cout << "Your number is " << fred->get_id() << "\n";
                                        ^
multi.cpp:18:20: note: member found by ambiguous name lookup
    virtual string get_id() const
                   ^
multi.cpp:8:20: note: member found by ambiguous name lookup
    virtual string get_id() const
                   ^
multi.cpp:51:44: error: no member named 'student_id' in 'Employee'
    cout << "Your number is " << new_hire->student_id() << "\n";
                                 ~~~~~~~~  ^
2 errors generated.
nkirov@cpp %

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

 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
#include<iostream>
using namespace std;

class Person  {
public:
    Person(string n) { name = n; }
    string get_name() const
    { return name; }
private:
    string name;
};

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

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

class TeachingAssistant : public Employee, public Student {
public:
    TeachingAssistant(string n): Employee(n), Student(n),
    Person(n)
    {}
//    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("Fred");
    Employee* new_hire = fred; // Legal, because a TeachingAssistant is-a Employee
    Student* advisee = fred;  // Legal, because a TeachingAssistant is-a Student
    cout << "Your name is " << fred->get_name() << endl;
    
    Student* mary = new Student("Mary");
 //   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;
}
nkirov@cpp % c++ double.cpp
nkirov@cpp % ./a.out
Your name is Fred
No, mary is not a TeachingAssistant.

Solving the Diamond Problem with Virtual Inheritance

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