MySQL Java JavaScript PHP Python HTML-CSS C-sharp C++ Go

Moștenirea multiplă

O clasă derivată poate avea mai multe clase de bază directe. Acest tip de moștenire se numește moștenire multiplă, spre deosebire de moștenirea simplă, unde este folosită o singură clasă de bază. Deoarece complică ierarhia de moștenire, moștenirea multiplă este folosită mai rar.

Să analizăm un exemplu simplu:

#include <iostream>

class Camera    // clasă pentru cameră foto
{
public:
    void makePhoto()
    {
        std::cout << "making photo" << std::endl;
    }
};

class Phone     // clasă pentru telefon
{
public:
    void makeCall()
    {
        std::cout << "making call" << std::endl;
    }
};

// clasă pentru smartphone
class Smartphone : public Phone, public Camera
{ };

int main()
{
    Smartphone iphone;
    iphone.makePhoto();     // making photo
    iphone.makeCall();      // making call
}

Aici, clasa Camera reprezintă o cameră foto și oferă funcția makePhoto pentru a face poze. Clasa Phone oferă funcția makeCall pentru apeluri. Ambele clase sunt moștenite de clasa Smartphone, care poate atât să facă poze, cât și să efectueze apeluri.

Este important de observat că, la moștenire, se indică un specificator de acces pentru fiecare clasă de bază:

class Smartphone : public Phone, public Camera

Astfel, printr-un obiect Smartphone putem apela funcțiile ambelor clase de bază:

Smartphone iphone;
iphone.makePhoto();     // making photo
iphone.makeCall();      // making call

Constructori și destructori

În moștenirea multiplă trebuie de asemenea să apelăm constructorii claselor de bază dacă aceștia au parametri. De exemplu, să presupunem că avem o clasă Book, o clasă File și o clasă Ebook care le moștenește:

#include <iostream>

class Book    // clasă pentru carte
{
public:
    Book(unsigned pages): pages(pages)
    {
        std::cout << "Book created" << std::endl;
    }
    ~Book()
    {
        std::cout << "Book deleted" << std::endl;
    }
    void printPageCount()
    {
        std::cout << pages << " pages" << std::endl;
    }
private:
    unsigned pages;
};

class File    // clasă pentru fișier
{
public:
    File(double size): size(size)
    {
        std::cout << "File created" << std::endl;
    }
    ~File()
    {
        std::cout << "File deleted" << std::endl;
    }
    void printSize()
    {
        std::cout << size << "Mb" << std::endl;
    }
private:
    double size;
};

// clasă pentru carte electronică
class Ebook : public Book, public File
{
public:
    Ebook(std::string title, unsigned pages, double size):
        Book{pages}, File{size}, title{title}
    {
        std::cout << "Ebook created" << std::endl;
    }
    ~Ebook()
    {
        std::cout << "Ebook deleted" << std::endl;
    }
    void printTitle()
    {
        std::cout << "Title: " << title << std::endl;
    }
private:
    std::string title;
};

int main()
{
    Ebook cppbook {"About C++", 320, 5.6};
    cppbook.printTitle();
    cppbook.printPageCount();
    cppbook.printSize();
}

Ambele clase de bază au constructori cu parametri. În constructorul lui Ebook apelăm acești constructori:

class Ebook : public Book, public File
{
public:
    Ebook(std::string title, unsigned pages, double size):
        Book{pages}, File{size}, title{title}

Merită să acordăm atenție ordinii de apelare a constructorilor. În definiția clasei Ebook, primul dintre clasele de bază este Book, așa că mai întâi se apelează constructorul clasei Book, iar abia apoi constructorul clasei File.

Pentru fiecare clasă este definit și un destructor. Să observăm ordinea apelării constructorilor și destructorilor. Pentru asta, în funcția main vom crea un obiect Ebook și vom apela toate funcțiile claselor de bază:

int main()
{
    Ebook cppbook {"About C++", 320, 5.6};
    cppbook.printTitle();
    cppbook.printPageCount();
    cppbook.printSize();
}

Rezultatul afișat în consolă va fi:

Book created  
File created  
Ebook created  
Title: About C++  
320 pages  
5.6Mb  
Ebook deleted  
File deleted  
Book deleted

Vedem că primul este apelat constructorul clasei Book, deoarece ea este prima în lista claselor de bază. Destructorii sunt apelați în ordine inversă. Astfel, destructorul clasei Book se execută ultimul.

Ambiguitate în cazul numelor identice

În exemplul anterior, toate clasele aveau funcții cu nume diferite. Să vedem ce se întâmplă când două clase de bază au o funcție cu același nume:

#include <iostream>

class Book
{
public:
    Book(unsigned pages): pages(pages) { }
    void print()
    {
        std::cout << pages << " pages" << std::endl;
    }
private:
    unsigned pages;
};

class File
{
public:
    File(double size): size(size) { }
    void print()
    {
        std::cout << size << "Mb" << std::endl;
    }
private:
    double size;
};

class Ebook : public Book, public File
{
public:
    Ebook(std::string title, unsigned pages, double size):
        Book{pages}, File{size}, title{title}
    { }
    void printTitle()
    {
        std::cout << "Title: " << title << std::endl;
    }
private:
    std::string title;
};

int main()
{
    Ebook cppbook {"About C++", 320, 5.6};
    cppbook.print();    // Eroare de compilare
}

Aici, clasele Book și File au ambele o funcție print(). Apare o ambiguitate, iar codul nu se poate compila.

Pentru a rezolva problema, trebuie să specificăm explicit din ce clasă dorim să apelăm funcția:

int main()
{
    Ebook cppbook {"About C++", 320, 5.6};
    cppbook.Book::print();    // 320 pages
    cppbook.File::print();    // 5.6Mb
}

Alternativ, putem face o conversie de tip și apoi apela funcția:

int main()
{
    Ebook cppbook {"About C++", 320, 5.6};
    static_cast<Book&>(cppbook).print();    // 320 pages
    static_cast<File&>(cppbook).print();    // 5.6Mb
}

Moștenire multiplă și clase de bază virtuale

O altă formă de ambiguitate apare atunci când o clasă derivată moștenește mai multe clase care, la rândul lor, moștenesc aceeași clasă de bază, fie direct, fie indirect. De exemplu:

#include <iostream>

class Person
{
public:
    Person(std::string name): name{name} 
    { 
        std::cout << "Person created" << std::endl;
    }
    ~Person() 
    { 
        std::cout << "Person deleted" << std::endl;
    }
    void print() const
    {
        std::cout << "Person " << name << std::endl;
    }
private:
    std::string name;
};

class Student: public Person
{
public:
    Student(std::string name): Person{name} {}
};

class Employee: public Person
{
public:
    Employee(std::string name): Person{name} {}
};

class StudentEmployee: public Student, public Employee
{
public:
    StudentEmployee(std::string name): Student{name}, Employee{name} {}
};

int main()
{
    StudentEmployee bob{"Bob"};
    //bob.print();
}

Clasa Person este moștenită atât de Student, cât și de Employee. Pentru a modela un „student angajat”, definim clasa StudentEmployee, care le moștenește pe amândouă.

Această structură duce la ambiguitate și, dacă rulăm programul, vom vedea că pentru un singur obiect StudentEmployee sunt apelate de două ori constructorul și destructorul clasei Person:

Person created  
Person created  
Person deleted  
Person deleted

Mai mult, apelul bob.print() nu se poate compila.

Pentru a rezolva problema, în C++ se folosesc clase de bază virtuale: la moștenire se adaugă cuvântul-cheie virtual înaintea clasei de bază. Să aplicăm această soluție:

class Person
{
public:
    Person(std::string name): name{name} 
    { 
        std::cout << "Person created" << std::endl;
    }
    ~Person() 
    { 
        std::cout << "Person deleted" << std::endl;
    }
    void print() const
    {
        std::cout << "Person " << name << std::endl;
    }
private:
    std::string name;
};
 
class Student: public virtual Person
{
public:
    Student(std::string name): Person{name} {}
};
class Employee: public virtual Person
{
public:
    Employee(std::string name): Person{name} {}
};
// работающий студент
class StudentEmployee: public Student, public Employee
{
public:
    StudentEmployee(std::string name): Person{name}, Student{name}, Employee{name}  {}
};
 
int main()
{
    StudentEmployee bob{"Bob"};
    bob.print();
}

Acum, în definițiile claselor Student și Employee, clasa de bază Person este specificată ca fiind virtuală:

class Student: public virtual Person  
class Employee: public virtual Person

Ca urmare, pentru obiectul StudentEmployee vom putea apela funcția print:

int main()
{
    StudentEmployee bob{"Bob"};
    bob.print();
}

Iar rezultatul afișat în consolă va fi:

Person created  
Person Bob  
Person deleted

Astfel, observăm că acum constructorul și destructorul clasei Person sunt apelați doar o singură dată.