Если необходимо обеспечить динамическое связывание при передаче параметров в функцию, то такой параметр должен представлять ссылку или указатель на объект базового типа:
#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) }
В данном случае функция printPerson
в качестве параметра принимает константную ссылку на объект типа Person, коим в реальности также может быть объект Employee. Поэтому при вызове функции
print программа будет динамически решать, какую именно реализацию функции вызвать.
Объекты базовых и производных классов можно хранить в одной коллекции, например, массиве. Например:
#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(); } }
Здесь массив people хранит объекты Person, в качестве которых также могут выступать объекты Employee. Однако при такой организации каждый объект Employee, который помещается в массив, преобразуется в объект Person. В итоге при переборе такого массива вызывается функция print из класса Person:
Tom Bob Sam
Если мы хотим обеспечить для элементов массива динамическое связывание, то такие объекты должны представлять указатели. Например, используем указатели:
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(); } }
Здесь массив хранит адреса всех объектов, соотвественно получим совсем другой вывод:
Tom Bob (Microsoft) Sam (Google)
Деструктор определяет логику удаления класса. При удалении объекта производного класса мы ожидаем, что будет выполняться деструктор производного, а затем и деструктор базового классов, что позволяет выполнить необходимую логику (например, освобождение выделенной памяти) для обоих классов. Однако в некоторых ситуациях такое может не сработать. Например:
#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(); }
Здесь переменная sam представляет smart-указатель std::unique_ptr
на объект Person, который автоматически выделяет память для одного объекта Employee. Поскольку
объект Employee - это одновременно объект Person, то никакой проблемы в данном случае не будет.
Для обоих классов определены деструкторы, который просто выводят строку на консоль. То есть мы ожидаем, что после завершения функции main объект указателя sam будет удален, и будут выполняться деструкторы классов Employee и Person (ведь у нас объект Employee). Но что нам покажется в реальности консоль:
Sam (Google) Person Sam deleted
А консоль нам показывает, что для объекта по указателю sam вызывается только деструктор класса Person, хотя объект то у нас Employee. Это может иметь неприятные последствия, особенно, если в конструкторе Employee выделяем память, а в деструткоре Employee освобождаем. Чтобы все-таки деструктор Employee вызывался, нам надо определить деструктор базового класса как виртуальный. Итак, изменим код деструктора класса Person, добавив перед ним слово virtual:
virtual ~Person() { std::cout << "Person " << name << " deleted" << std::endl; }
Весь остальной код остается прежним. И теперь мы получим другой консольный вывод:
Sam (Google) Employee Sam deleted Person Sam deleted
Таким образом, теперь вызывается деструктор обоих классов.
Стоит отметить, что виртуальные функции позволяют нам обойти ограничения на доступ к функциям. Например, сделаем функцию print в классе Employee приватной:
#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(); // так нельзя - функция приватная person->print(); // а так можно }
Поскольку теперь функция print в Employee приватная, мы не можем вне класса вызвать эту функцию напрямую для объекта Employee:
Employee bob {"Bob", "Microsoft"}; bob.print(); // так нельзя - функция приватная
Зато можем вызвать эту реализацию через указатель на тип Person:
Person* person {&bob}; person->print(); // а так можно