Index

Moștenirea multiplă. Moștenirea în diamant
Funcții virtuale
Funcții virtuale pure
Clase abstracte
Constructori, destructori și virtualizare
Downcasting

Moștenirea multiplă

Moștenirea din mai multe clase

Moștenirea în diamant

Moștenirea în diamant apare atunci când o clasă derivată moștenește din două clase intermediare care, la rândul lor, moștenesc din aceeași clasă de bază.

Exemplu:

class B
{
public:
    int i;
};

class Derived1 : public B
{
public:
    int j;
};

class Derived2 : public B
{
public:
    int k;
};

class Derived3 : public Derived1, public Derived2 // contine de doua ori B
{
public:
    int sum;
};

int main()
{
    Derived3 ob;
    ob.j = 2; // merge
    ob.j = 6; // merge
    ob.i = 10; // ambiguitatea, de 2 ori i
    // orice accesare a lui i produce ambiguitate
    return 0;
}

Vizualizare:

classDiagram
    class B {
        int i
    }

    class Derived1 {
        int j
    }

    class Derived2 {
        int k
    }

    class Derived3 {
        int sum
    }

    B --> Derived1
    B --> Derived2
    Derived1 --> Derived3
    Derived2 --> Derived3

Soluția este prin moștenirea virtuală.

Moștenirea virtuală

Info

Moștenirea virtuală este un mecanism al C++ care permite evitarea duplicării unei clase de bază atunci când aceasta este moștenită pe mai multe căi într-o ierarhie de clase.

class B {
public:
    int val;
};

class D1 : virtual public B { };
class D2 : virtual public B { };

class D3 : public D1, public D2 { };

int main() {
    D3 obj;
    obj.val = 10; // funcționează — este o singură variabilă val
}

Funcții virtuale

Definiție

O funcție virtuală este o funcție membră care poate fi redefinită (overridden) într-o clasă derivată și care permite selectarea dinamică (la execuție) a versiunii corecte a funcției în funcție de tipul real al obiectului, nu după tipul declarat. (polimorfism la execuție)

Caracteristici:

class Baza {
public:
    virtual void afiseaza() {
        cout << "Funcție din clasa Baza\n";
    }
};

class Derivata : public Baza {
public:
    void afiseaza() override {
        cout << "Funcție din clasa Derivata\n";
    }
};

int main() {
    Baza* ptr;
    Derivata d;
    ptr = &d;
    ptr->afiseaza(); // → execută funcția din Derivata
}

Avantaje:

Upcasting

Tipul derivat poate lua locul tipului de bază. Funcțiile virtuale ne permit să chemăm funcțiile pentru tipul derivat.

#include <iostream>
enum note{C, Esharp, Eflat};

class Instrument
{
public:
    void play(note)
    {
        std::cout<<"Instrument::play"<<std::endl;
    }
};

class Winds : public Instrument
{
public:
    void play(note)
    {
        std::cout<<"Winds::play"<<std::endl;
    }
};

void tune(Instrument& instr)
{
    instr.playEsharp;
}

int main()
{
    Winds trombone;
    tune(trombone); // va afisa Instrument::play
    return 0;
}

Dacă folosim virtual:

class Instrument{
public:
	virtual play(note);
}

class Winds{
public:
	virtual play(note) override;
}

int main() 
{
	Winds trombone;
	tune(trombone); // va afisa chiar daca tune primeste referinta tip Instrument
}

Late binding (dynamic dispatch)

Late binding --> se face prin pointeri (sau referințe), dacă funcțiile sunt apelate prin valoare, nu există late binding.

#include <iostream>
using namespace std;

class Pet { public:
    virtual string speak() const { return " "; } };

class Dog : public Pet { public:
    string speak() const { return "Bark!"; } };

int main() {
    Dog ralph;
    Pet* p1 = &ralph;
    Pet& p2 = ralph;
    Pet p3;
    // Late binding for both:
    cout << "p1->speak() = " << p1->speak() <<endl;
    cout << "p2.speak() = " << p2.speak() << endl;
    cout << "p3.speak() = " << p3.speak() << endl;
}

Funcții virtuale pure

Sintaxa:

virtual tip_returnat nume_func(args) = 0;

De exemplu:

virtual int pura(int i) = 0;

Clase Abstracte

O clasă abstractă este o clasă ce are cel puțin o funcție virtuală pură.

Necesitate: vrem o clasă care dă doar interfața (nu vrem obiecte din clasă abstractă, ci upcasting la ea).

Eroare la instanțiere

Nu pot fi definite obiecte ale unei clase abstracte.

Este permisă utilizarea de pointeri și referințe către clasa abstractă (pentru upcasting).

Moștenire de la clase abstracte

Dacă o funcție virtuală nu e suprascrisă, atunci clasa derivată rămâne abstractă

Altă utilitate importantă: previne object slicing.

Overload pe funcții virtuale

  • NU este posibil overload prin schimbarea tipului de parametru de întoarcere
  • este posibil overload prin mai mulți parametri/tip diferit de parametri

Excepție

Poți suprascrie o funcție virtuală care întoarce un pointer către clasa de bază cu o funcție care întoarce un pointer către clasa derivată.

class B {
public:
    virtual B* clone() const {
        return new B(*this);
    }
};

class D : public B {
public:
    D* clone() const override { // works
        return new D(*this);
    }
};

Constructori, destructori și virtualizare

Important

Nu putem avea constructori virtuali.

Destructorii virtuali -> sunt uzuali.

Dacă vrem să eliminăm porțiuni alocate dinamic și pentru clasa derivată dar facem upcasting trebuie să folosim destructori virtuali.

class Base {
public:
    virtual ~Base() {
        cout << "Base destructor\n";
    }
};

class Derived : public Base {
public:
    ~Derived() override {
        cout << "Derived destructor\n";
    }
};

int main() {
    Base* obj = new Derived;
    delete obj;  // Dacă ~Base() NU este virtual, ~Derived() NU se apelează!
}
Destructori virtuali puri
class AbstractBase {
public:
	virtual ~AbstractBase() = 0;
};

AbstractBase::~AbstractBase() {}
Apel funcții virtuale în destructor

În destructor, la apelul funcțiilor virtuale de face early binding.

class Base { public:
  virtual ~Base() {  
	  cout << "~Base1()\n";  
	  this->f();   // early binding, de asta se afseaza Base::f()
  }
  virtual void f() { cout << "Base::f()\n"; }
};
class Derived : public Base { public:
  ~Derived() { cout << "~Derived()\n"; }
  void f() { cout << "Derived::f()\n"; }
};

int main() {
  Base* bp = new Derived; 
  delete bp; // Afis: ~Derived() ~Base1() Base::f()
}

Vezi continuarea aici

Downcasting

Este folosit în ierarhii cu clase polimorfice (în care se folosesc funcții virtuale). E problematic, dacă obiectul nu e într-adevăr derivat.

Tip Efect
Upcasting Base* <-- Derived*
Downcasting Base* --> Derived*

dynamic_cast

Base* b = new Derived;
Derived* d = dynamic_cast<Derived*>(b);

If doesn't point to a Derived, returns nullptr.

static_cast

class Pet { public: virtual ~Pet(){}};
class Dog : public Pet {};
class Cat : public Pet {};

int main() {
  Pet* b = new Cat; // Upcast
  Dog* d1 = dynamic_cast<Dog*>(b); //Try to cast it to Dog*:
  Cat* d2 = dynamic_cast<Cat*>(b);// Try to cast it to Cat*:
  cout << "d1 = " << d1 << endl; // 0 -> nullptr
  cout << "d2 = " << d2 << endl; // same adress as b
cout << "b  = " << b << endl;
}