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
Обща препоръка е да се избягва многократна наследственост.