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

Particularități ale legării dinamice

Legarea dinamică la transmiterea parametrilor

Dacă dorim să asigurăm legarea dinamică la transmiterea parametrilor către o funcție, atunci acest parametru trebuie să fie o referință sau un pointer la un obiect de tip de bază:

#include <iostream>

class Person
{
public:
    Person(std::string name): name{name} { }
    virtual void print() const
    {
        std::cout << name << std::endl;
    }
    std::string getName() const { return name; }
private:
    std::string name;
};

class Employee: public Person
{
public:
    Employee(std::string name, std::string company): Person{name}, company{company} { }
    void print() const override
    {
        std::cout << getName() << " (" << company << ")" << std::endl;
    }
private:
    std::string company;
};

void printPerson(const Person& person) 
{
    person.print();
}

int main()
{
    Person tom {"Tom"};
    Employee bob {"Bob", "Microsoft"};
    printPerson(tom);   // Tom
    printPerson(bob);   // Bob (Microsoft)
}

În acest caz, funcția printPerson primește ca parametru o referință constantă la un obiect de tip Person, care în realitate poate fi și un obiect Employee. Prin urmare, la apelul funcției print, programul va decide dinamic ce implementare a funcției să execute.

Legarea dinamică și colecțiile

Obiectele claselor de bază și derivate pot fi stocate într-o singură colecție, cum ar fi un array. De exemplu:

#include <iostream>

class Person
{
public:
    Person(std::string name): name{name} { }
    virtual void print() const
    {
        std::cout << name << std::endl;
    }
    std::string getName() const { return name; }
private:
    std::string name;
};

class Employee: public Person
{
public:
    Employee(std::string name, std::string company): Person{name}, company{company} { }
    void print() const override
    {
        std::cout << getName() << " (" << company << ")" << std::endl;
    }
private:
    std::string company;
};

void printPerson(const Person& person) 
{
    person.print();
}

int main()
{
    Person tom {"Tom"};
    Employee bob {"Bob", "Microsoft"};
    Employee sam {"Sam", "Google"};
    Person people[]{tom, bob, sam};
    for(const auto& person: people)
    {
        person.print();
    }
}

Aici, array-ul people conține obiecte de tip Person, dar printre ele se află și obiecte Employee. Totuși, în această structură, fiecare obiect Employee este convertit într-un obiect Person. Astfel, la iterare se apelează doar funcția print din clasa Person:

Tom  
Bob  
Sam

Dacă dorim ca elementele array-ului să suporte legare dinamică, obiectele trebuie să fie stocate sub formă de pointeri:

int main()
{
    Person tom {"Tom"};
    Employee bob {"Bob", "Microsoft"};
    Employee sam {"Sam", "Google"};
    Person* people[]{&tom, &bob, &sam};
    for(const auto& person: people)
    {
        person->print();
    }
}

Acum array-ul conține adresele obiectelor, deci vom obține:

Tom  
Bob (Microsoft)  
Sam (Google)

Destructori virtuali

Destructorul definește logica de distrugere a clasei. Când ștergem un obiect al unei clase derivate, ne așteptăm ca destructorul clasei derivate să fie apelat primul, urmat de cel al clasei de bază. Aceasta asigură eliberarea resurselor pentru ambele clase. Totuși, uneori acest comportament nu se produce. De exemplu:

#include <iostream>
#include <memory>

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

class Employee: public Person
{
public:
    Employee(std::string name, std::string company): Person{name}, company{company} { }
    ~Employee()
    {
        std::cout << "Employee " << getName() << " deleted" << std::endl;
    }
    void print() const override
    {
        std::cout << getName() << " (" << company << ")" << std::endl;
    }
private:
    std::string company;
};

void printPerson(const Person& person)
{
    person.print();
}

int main()
{
    std::unique_ptr<Person> sam { std::make_unique<Employee>("Sam", "Google") };
    sam->print();
}

Aici, variabila sam este un smart-pointer std::unique_ptr la un obiect Person, care alocă automat un obiect Employee. Deși Employee este și un Person, consola ne va arăta:

Sam (Google)  
Person Sam deleted

Se apelează doar destructorul clasei Person, deși obiectul este de tip Employee. Dacă în constructorul Employee s-a alocat memorie, iar destructorul trebuia să o elibereze, acest lucru nu se va întâmpla. Pentru a rezolva, facem destructorul clasei de bază virtual:

virtual ~Person()
{
    std::cout << "Person " << name << " deleted" << std::endl;
}

Acum ieșirea va fi:

Sam (Google)  
Employee Sam deleted  
Person Sam deleted

Suprascrierea specificatorului de acces

Funcțiile virtuale ne permit să evităm restricțiile de acces. De exemplu, dacă facem funcția print din Employee privată:

#include <iostream>

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

class Employee: public Person
{
public:
    Employee(std::string name, std::string company): Person{name}, company{company}
    { }
private:
    void print() const override
    {
        Person::print();
        std::cout << "Works in " << company << std::endl;
    }
    std::string company;
};

int main()
{
    Employee bob {"Bob", "Microsoft"};
    Person* person {&bob};
    //bob.print();         // nu este permis – funcția este privată
    person->print();       // dar acest apel este permis
}

Pentru că acum funcția print din clasa Employee este privată, nu o putem apela direct din afara clasei asupra unui obiect de tip Employee:

Employee bob {"Bob", "Microsoft"};
bob.print();    // acest lucru nu este permis – funcția este privată

Însă putem apela această implementare printr-un pointer la tipul Person:

Person* person {&bob};
person->print();        // acest lucru este permis

Deoarece funcția este virtuală și este definită în clasa de bază ca fiind publică, apelul prin pointerul de tip Person este permis chiar dacă implementarea din clasa derivată este privată. Astfel, mecanismul de legare dinamică permite accesul la implementarea privată din clasa derivată prin intermediul interfeței publice a clasei de bază.