1 Одиночное наследование

1.1 Понятие наследования

При большом количестве никак не связанных классов в программе, управлять ими становится практически невозможным (так же, как и большим количеством функций в большом проекте). Наследование позволяет справиться с этой проблемой путем упорядочивания и ранжирования классов, то есть объединения общих для нескольких классов свойств в одном классе и использования его в качестве базового.


Наследование - механизм передачи свойств одних классов другим классам в процессе проектирования иерархии классов. Исходные классы называют базовыми(БК) (родителями), вторые - производными (ПК) (потомками).


Наследование может быть одиночным и множественным, в зависимости от числа родителей.

Классы, находящиеся ближе к началу иерархии, объединяют в себе наиболее общие черты для всех нижележащих классов. По мере продвижения вниз по иерархии классы приобретают все больше конкретных черт. Множественное наследование позволяет одному классу обладать свойствами двух и более родительских классов.

Пример наследования


Пример с наследованием

class Box    // тип ``ящик``
{
  protected:
    int width,height;
  public:
    void SetWidth(int w)  { width=w;  }
    void SetHeight(int h) { height=h; }
};
class ColoredBox : public Box  // ``цветной ящик``
{
   int color;
 public:
   void SetColor(int c)  { color=c; }
};

1.2 Ключи наследования

При описании производного класса в его заголовке перечисляются все базовые для него классы. Доступ к элементам этих классов регулируется с помощью ключей доступа: private, protected и public. Если базовых классов несколько, они перечисляются через запятую.


Использование ключей доступа вместе с разделами внутри БК позволяет сформировать правило, по которому будет осуществляться доступ к членам ПК.

Ключ доступа Раздел БК Раздел ПК
private private доступ через БК
protected private
public private
protected private доступ через БК
protected protected
public protected
public private доступ через БК
protected protected
public public

Из таблицы следует, что если, например, в БК некоторая переменная располагалась в разделе public, а ПК был объявлен с ключом private, то в ПК к данной переменной можно будет обращаться только членам ПК или его друзьям (эта переменная перейдет в раздел private ПК).

Когда происходит наследование без явного указания спецификатора, все имена базового класса в производном классе автоматически становятся приватными (или можно указать private).


Если наследовать с ключевым словом public - все общедоступные имена базового класса будут общедоступными в производном классе и все защищенные имена будут защищенными в производном классе.

1.3 Правила

Нельзя наследовать

  1. конструкторы
  2. деструкторы
  3. перегруженные new
  4. перегруженные =
  5. отношения дружественности

Правила для специальных методов

  • Поскольку конструкторы не наследуются, ПК должен иметь собственные конструкторы.
  • Если в конструкторе производного класса явный вызов конструктора базового класса отсутствует, автоматически вызывается конструктор БК.
  • Для иерархии, состоящей из нескольких классов, конструкторы БК вызываются начиная с самого верхнего уровня. После этого выполняются конструкторы элементов класса, являющихся объектами, а затем - конструктор класса.
  • В случае нескольких БК их конструкторы вызываются в порядке объявления.
  • Для деструкторов эти правила справедливы, но порядок вызова обратный - сначала ПК, затем БК. Не требуется явно вызывать деструкторы, поскольку они будут вызваны автоматически.

Передача параметра в конструктор БК

Данная ситуация возникает в том случае, когда конструктор БК должен быть вызван с параметром (параметрами). При записи конструктора ПК необходимо указать через двоеточие имя конструктора БК со списком фактических параметров.



class X   {
   int a,b,c;
  public:
   X(int x,int y,int z) { a=x; b=y; c=z; }
};
class Y : public Х  {
   int val;
  public:
   Y(int d) : X(d,d+1,d+5) { val=d; }
   Y (int d, int e);
}
Y::Y(int d, int e) : X(d,e,12)  {
   val=d+e;
}

1.4 Производные классы

class Employee {
public:
  Employee(string _name,
           string _surname);
  ~Employee();
  void hire(Date d);
  void fire(Date d);
  string name() const;
  string surname() const;
  void print() const;
private:
  string name, surname;
  Date hire_date, fire_date;
};

class Programmer: public Employee
{
public:
  Programmer(string _name,
             string _surname,
             string _team);
  ~Programmer();
  void set_team (string _team);
  string team() const;
  void print() const;
private:
  string team;
};



Employee::Employee()
Employee::~Employee()
Employee::hire()
Employee::fire()
Employee::name()
Employee::surname()
Employee::print()

Programmer::Programmer()
Programmer::~Programmer()
Programmer::set_team()
Programmer::team()
Programmer::print()

Programmer::Employee::hire()
Programmer::Employee::fire()
Programmer::Employee::name()
Programmer::Employee::surname()
Programmer::Employee::print()



Date start_date(1,1,2004), end_date(31,12,2007);

Employee emp("Vasya", "Pupkin");
emp.hire(start_date);
cout << emp.name();
cout << emp.surname();
emp.print()
emp.fire(end_date);

Programmer prog("Petr", "Petrov", "GM00");
prog.hire(start_date);
prog.set_team("GM12");
cout << prog.name();
cout << prog.surname();
cout << prog.team();
prog.print();
prog.Employee::print()
prog.fire(end_date);

Производные классы и указатели

Programmer *prog1 = new Programmer("Petr", "Petrov", "GM12");
Employee *emp1 = prog1;   // хорошо
Employee *emp2 = new Employee("vasya", "Pupkin");
Programmer *prog2 = emp2; // ошибка 
prog2->set_team("GM00");  // нет team

void test_function(Employee& emp);

Programmer prog3("Ivan", "Ivanov", "GM00");
test_function (prog3);  // хорошо


С объектом производного класса можно обращаться как с объектом базового класса при обращении к нему при помощи указателей и ссылок.

1.5 Функции-члены

class Employee {
  string name, surname;
  //...
public:
 void print() const;
 string surname() const;
};

class Programmer: public Employee { //...
public:
 void print() const;
 //...
};

void Programmer::print() const
{
  cout << surname() << endl;
}
void Programmer::print() const
{
  cout << surname << endl;
}
void Programmer::print() const
{
  Employee::print();
  cout << team << endl;
}



int main()
{
  Employee emp("Vasya", "Ivanov");
  Programmer prog("Petr", "Petrov", "GM12");
 
  emp.print(); // Vasya Ivanov
  emp.surname(); // Ivanov
 
  prog.print(); // Petr Petrov GM12
  prog.surname(); // Petrov
}

1.6 Конструкторы

class Employee {
  string name, surname;
public:
  Employee(const string&,
           const string&);
};

class Programmer:
              public Employee 
{
  string team;
public:
  Programmer(const string&,
             const string&,
             const string&);
  //...
};

Employee::Employee(const string& n,
                  const string& sn)
  : name(n), surname(sn)
{ /*...*/ }

Programmer::Programmer(
  const string& _name,
  const string& _surname,
  const string& _team) :
    Employee(_name, _surname),
    team(_team)
{
  //...
}

class Employee {
  string name, surname;
public:
  Employee(const string&,
           const string&);
};

class Programmer:
              public Employee {
  string team;
public:
  Programmer(const string&,
             const string&,
             const string&);
  //...
};

Programmer::Programmer(
  const string& name,
  const string& sname,
  const string& t) :
    name(name),
    surname(sname),
    team(t)
{
  //...
}

1.7 Копирование



class Employee {
  //...
  Employee(const Employee&);
  Employee& operator=(const Employee&)
};

void f(const Programmer& rPrg)
{
   Employee emp = rPrg;
   emp = rPrg;
};


Копируется только Employee-часть Programmer – срезка.



class Employee {
  string name, surname;
public:
  Employee(const Employee&);
  Employee& operator=(const Employee&)
  //...
};



class Programmer: public Employee {
  string team;
public:
   Programmer(const Programmer &);
   Programmer& operator=(const Programmer &)
  //...
};



Programmer::Programmer (const Programmer& rp)
  : Employee(rp), team(rp.team)
{
}

Programmer& Programmer::operator=(const Programmer &rp)
{
  Employee::operator=(rp);
  team = rp.team;
}


  • operator= не наследуется
  • Конструкторы не наследуются

1.8 Иерархия

Иерархия классов



class Employee {/*...*/};
class Programmer: public Employee
{/*...*/};
class Team_leader: public Programmer
{/*...*/};
class Proj_manager: public Employee
{/*...*/};
class Senior_Manager: public Proj_manager
{/*...*/};
class HR_manager: public Employee
{/*...*/};



class Team_leader: public Programmer {
public:
  Team_leader(string n, string fn, string t);
  bool add_designer(Programmer*);
  bool rm_designer(string fn, string n);
  Programmer* get_designer(string fn, string n) const;
private:
  list<Programmer*> team_list;
};



Team_leader::Team_leader(string n,string fn, string t) :
                Programmer(n, fn, t),team_list()
{}
...
Team_leader tm("Igor", "Kotov", "GM12");
tm.hire(Date(20,3,2008));
cout << tm.name();
tm.set_team("GM18");
tm.add_designer(p);
tm.fire(Date());

2 Виртуальные функции

2.1 Введение

Виртуальные функции



class Employee {
public:
  Worker(string _name,
         string _surname);

  // ...
  virtual void print() const;

private:
  string name, surname;
  Date hire_date, fire_date;
};



class Programmer: 
             public Employee
{
public:
  Programmer(string _name,
             string _surname,
             string _team);

  virtual void print() const;

private:
  string team;
};



void Employee::print() const
{
 cout << first_name << " " << surname    << endl;
}



void Programmer::print() const
{
  Employee::print();
  cout << "team: " << team << endl;
}



void print_emp(const Employee* pEmp)
{
  cout << "Employee info:" << endl;
  pEmp->print();
}



int main()
{
  Employee emp("Vassya", "Pupkin");
  Programmer prog("Ivan", "Sidorov", "GM12");
  print_emp ( &emp );
  print_emp ( &prog );
  return 0;
}

2.2 Реализация

Таблица виртуальных функций

2.3 Примеры использования

Когда используется виртуальность



Employee emp("Vasya", "Pupkin");
Programmer prog("Ivan", "Sidorov", "GM12");
emp.print();  // нет, Employee::print()
prog.print(); // нет, Programmer::print()

void fn1(Employee *p)
{
  p->print(); // да
  p->hire();  // да
}

void fn2(Employee &r)
{
  r.print();  // да
  r.fire();   // да
}

Механизм виртуальности используется, только когда вирт. функция вызывается через указатель или ссылку на базовый класс.



// массив указателей на Employee, размер
void give_a_bonus(Employee *list[], int size)
{
  for(int i=0; (i<size && list[i]); ++i)
	list[i]->bonus();
}



void create_lucky_list_and_give_bonus()
{
   Employee **list = new (Employee*) [10];
   for(int i=0; i<10; ++i)
	list[i] = next_lucky_man();
   give_a_bonus(list);
}



class Unit {
public:
   virtual bool action()
             {return false};
};
class Soldier: public Unit {/*...*/};
class Tank : public Unit {/*...*/};
class Mine : public Unit {/*...*/};



void Field::turn()
{
  for(int i=0; i<unit_number; ++i)
      if ( units[i]->action() != true)
         move_to_end(units[i]);
}



class Field
{
public:
  Field();
  ~Field();
  void refresh_field();
  void turn();
  void move_to_end(Unit*);

private:
  int unit_number;
  Unit **units;
  //...
}

2.4 Абстрактные классы

class Cosmetics {
public:
  virtual void make_up() = 0;
  virtual void touch_up() = 0;
  virtual void remove() = 0;
};

class Lipstick :
 public Cosmetics {
public:
  virtual void make_up();
  virtual void touch_up();
  virtual void remove();
};

class Mascara :
  public Cosmetics {
public:
  virtual void make_up();
  virtual void touch_up();
};

class WaterRes_Mascara :
  public Mascara {
public:
  virtual void remove();
};

class Plain_Mascara :
  public Mascara {
public:
  virtual void remove();
};



Cosmetics cosmo;  // ошибка
Lipstick lips;
Mascara masc;     // ошибка
WaterRes_Mascara wr_masc;
Plain_Mascara plain_masc;



void complete_touch_up(Cosmetics* todo[], int n)
{
  for (int i=0; (i<n && todo[i]); ++i)
  {
      todo[i]->touch_up();
  }
}

class Shaver { // бритва
public:
  virtual void shave() = 0;
  virtual void reload() = 0;
  virtual void clean() = 0;
};

class Electric_Shaver :
 public Shaver {
public:
  virtual void shave();
  virtual void reload() {}
  virtual void clean();
};

class Razor : // станок
  public Shaver {
public:
  virtual void clean();
};

class Safe_Razor :
  public Razor {
public:
  virtual void shave();
  virtual void reload();
};

class Blade : // лезвие
  public Razor {
public:
  void shave();
  virtual void reload() {...}
};

  • Абстрактный класс – это чистый интерфейс
  • Класс асбтрактный, если есть хотя бы одна чисто виртуальная функция (=0)
  • Нельзя создать экземпляр абстрактного класса
  • Чисто виртуальная фнкция, которая не определена в производном классе, остается чисто виртуальной

2.5 Виртуальные конструкторы и деструкторы

Виртуальные деструкторы

class Employee
{
public:
  Employee(string _name,
           string _surname);
  virtual ~Employee();
};

class Programmer: 
         public Employee
{
public:
  Programmer(/*...*/);
  virtual ~Programmer();
};

void destroy_container(Empolyee **container, int size)
{
    for(int i=0; i < size; ++i) {
       delete container[i];
       container[i] = 0;
    }
}

Виртуальные ''конструкторы''

На самом деле, таких не существует!

class Employee
{
public:
  virtual Employee* new_employee() {return new Employee();}
  virtual Employee* clone() {return new Employee(*this);}
};

class Programmer
{
public:
  virtual Programmer* new_employee() {return new Programmer();}
  virtual Programmer* clone() {return new Programmer(*this);}
};

3 Стратегии наследования

Защищенные члены

class Employee {
  string name, surname;
protected:
  void retire();
  //...
};

class Programmer:
    public Employee {
  int team;
public:
  ~Programmer() { retire(); }
  //...
};

void ret(Employee &re,
         Programmer &rp) {
  re.retire(); // Ошибка!
  rp.retire(); // Ошибка!
}

class Proj_Manager: 
   public Employee
{ /*...*/};

class HR_Assistant: 
   public Employee {
   void fire(Proj_Manager *p) {
     p->retire(); // Ошибка!
     retire();
   } 
};

  • Производный класс имеет доступ к защищенным членам базового (но только для объектов собственного типа)
  • Защищенные данные приводят к проблемам сопровождения
  • Защищенные функции - хороший способ задания операций для использования в производных классах

Права доступа Public

class Employee {
public:
  string name() const;
protected:
  void retire();
};

class Programmer:
          public Employee {
  /*…*/
};

class Team_Leader: 
          public Programmer{
  /*…*/
};

void Team_Leader::~Team_Leader()
{
  retire();
}
void f(Employee *emp,
       Programmer *prog,
       Team_Leader *tleader)
{
  prog->name();
  tleader->name();
  prog->retire(); // Ошибка!
  emp = prog;
  prog = tleader;
  emp = tleader;
}

Права доступа Private

class Stack {
public:
  void push(char);
  char pop();
protected:
  int max_size();
  int cur_size();
private:
  int max_size;
  //...
};
class Tough_Stack: 
   private Stack {
public:
  void put(char c) { 
    push(c); 
  }
  char get() { 
    if (cur_size()>0)
      return pop(); 
    }
};

void f(Tough_Stack *p) {
   p->put('a');
   p->pop();         // Ошибка!
   Stack *pbase = p; // !
   pbase->pop();     // !
}

class Semi_Tough_Stack :
           public Tough_Stack {
public:
  char pop(Tough_Stack *p) {
    Stack *pbase = p;    // !
    return pbase->pop(); // !
  }
};

Правила доступа Protected

class Unit {
public:
  bool move (int x, int y);
  bool fire(int x, int y);
  bool no_ammo();
  bool reload();
  void retreat();
  void wound(int precent);
private:
  int X, Y;
  int ammo;
  int magazine;
  int health;
};

class Soldier: protected Unit {
public:
  bool move (int x, int y);
  bool shoot(int x, int y);
  void defend();
protected:
   void wound(int precent);
};

bool Soldier::move(int x, 
                   int y)
{
  return Unit::move(x,y);
}

bool Soldier::shoot(int x, 
                    int y)
{
  if (no_ammo())
  { if (reload()==false)
      return false;
  }
  return fire(x,y);
}

void Soldier::wound(int precent)
{
    Unit::wound(precent);
    if (health()<20)
       retreat();
}

void madness()
{
  Soldier sol;
  if (!sol.shoot(x,y))
  {
    sol.retreat();  // Ошибка!
  }

  if (sol.no_ammo()) // !
  {
    s1.wound(100);   // Ошибка!
  }
}

Доступ к базовым классам

  • Открытое наследование делает производный класс подтипом базового
  • Защищенное и закрытое наследование используются для выражения деталей реализации
  • Защищенные базовые классы полезны в иерархиях с дальнейщим наследованием
  • Закрытые базовые классы полезны для “ужесточения интерфейса”

4 Множественное наследование

4.1 Понятие множественного наследования

Множественное наследование

class Storable_Process :
 public Process,
 public Storable {
   //...
};

void f(Storable_Process& rSP)
{
  rSP.read(); // Storable::read()
  rSP.run();  // Process::run()
  rSP.dump(std::cerr);
  rSP.stop();  // Process::stop()
  rSP.write(); // Storable::write()
}

void start(Process*);
bool check_filename(Storable*);

void susp(Storable_Process* pSP)
{
  if ( check_filename(pSP) )
  {
     start(pSP);
  }
}

Виртуальные функции работают как обычно

class Process {
   //...
   virtual void pending() = 0;
};
class Storable {
   //...
   virtual void prepare() = 0;
};

class Storable_Process :
 public Process,
 public Storable {
   //…
   virtual void pending();
   virtual void prepare();
};

Разрешение неоднозначности

class Process {
  //...
  virtual debug_info* 
         get_debug();
};
class Storable {
  //...
  virtual debug_info* 
        get_debug();
};
void f(Storable_Process* pSP)
{
  debug_info* dip= 
       pSP->get_debug();
  dip= pSP->Storable::
            get_debug();
  dip= pSP->Process::
            get_debug();
}

class Storable_Process :
 public Process,
 public Storable {
  //...
  virtual debug_info* 
           get_debug()
  {
    debug_info* d1 =
           Storable::get_debug();
    debug_info* d2 =
           Process::get_debug();
    return d1->merge(d2);
  }
};

Пример

class Drink {
public:
  virtual Liquid* drink() =0;
};
class Bottle {
public:
  void fill(Liquid*);
  Liquid* pour(Volume);
  void open();
  void break();
  bool opened() const;
  bool empty() const;
};
class BBeer: public Drink,
             protected Bottle {
/*...*/ };

Liquid* BBeer::drink()
{
  if (!opened()) open();
  if (!empty())
    return pour(VOL_GULP));
  return NULL;
}
void get_drunk(BBeer* beer,
               Human *man)
{
  beer->break();
  man->consume(beer->drink());
  Bottle *bottle = &beer;
  bottle->break();
}

bool Human::get_drunk(Drink* alc[], int num)
{
   for(int i=0; i<num || i_am_drunk() ; ++i)
   {
     Liquid *p = 0;
     while( (p=acl[i]->drink()) !=0 )
     {
       consume(p);
     }
   }
   return i_am_drunk();
}

4.2 Дублирование

class Link {
  //...
  Link* next();
};
class Task_Queue: public Link {
  //...
};
class Processors: public Link {
  //...
};
class Distributor : public Task_Queue,
                    public Processors {
  //...
};

Для устранения дублирования используют виртуальные базовые классы.

class Storable {
public:
  Storable(const char*)
  virtual void read() =0;
  virtual void write() =0;
  virtual ~Storable() {
    write();
  }
private:
  const char* store;
};

class Transmitter: 
   public virtual Storable {
  //…
  virtual void write();
};

class Receiver: 
   public virtual Storable {
  //…
  virtual void write();
};

class Radio : 
    public Transmitter,
    public Receiver {
  Radio() : Storable(“radio.stor”)
  {}
  virtual void write()  {  
     Transmitter::write();
     Receiver::write();  
  }  
};