Если переменные или функции в базовом классе являются закрытыми, то есть объявлены со спецификатором private то, производный класс хотя и наследует эти переменные и функции, но не может к ним обращаться. К примеру, попробуем определить в производном классе функцию, которая выводит значения приватных переменных базового класса:
#include <iostream> class Person { public: Person(std::string name, unsigned age) { this->name = name; this->age = age; } void print() const { std::cout << "Name: " << name << "\tAge: " << age << std::endl; } private: // закрытые переменные - доступ из производного класса недоступен std::string name; // имя unsigned age; // возраст }; class Employee: public Person { public: Employee(std::string name, unsigned age, std::string company): Person(name, age) { this->company = company; } void printEmployee() const { std::cout << name << " works in " << company << std::endl; // ! Ошибка } private: std::string company; };
В базовом классе Person определены приватные переменные name и age. В производном классе Employee в функции printEmployee мы пытаемся обратиться к ним, чтобы вывести их значение на консоль. И в данном случае мы столнемся с ошибкой, так как переменные name и age - приватные переменные базового класса Person. И производный класс Employee к ним не имеет доступа.
Однако иногда возникает необходимость в таких переменных и функциях базового класса, которые были бы доступны в производных классах, но не были бы доступны извне. То есть тот же private, только с возможностью доступа для производных классов. И именно для определения уровня доступа подобных членов класса используется спецификатор protected.
Например, определим переменную name со спецификатором protected:
#include <iostream> class Person { public: Person(std::string name, unsigned age) { this->name = name; this->age = age; } void print() const { std::cout << "Name: " << name << "\tAge: " << age << std::endl; } protected: std::string name; // доступно из производных классов private: unsigned age; }; class Employee: public Person { public: Employee(std::string name, unsigned age, std::string company): Person(name, age) { this->company = company; } void printEmployee() const { std::cout << name << " works in " << company << std::endl; } private: std::string company; // компания }; int main() { Person person {"Tom", 38}; person.print(); // Name: Tom Age: 38 Employee employee {"Bob", 42, "Microsoft"}; employee.printEmployee(); // Bob works in Microsoft }
Таким образом, мы можем использовать переменную name в производном классе, например, в методе printEmployee, но извне базового и производного классов мы к ней обратиться по-прежнему не можем.
Как мы увидели, спецификатор доступа - public
, private
, protected
играют большую роль в том, к каким именно переменным и функциям базового класса могут
обращаться производные классы. Однако на доступ также влияет спецификатор доступа базового класса, применяемый при установке наследования:
class Employee: public Person
Так, в примере выше мы используем спецификатор public
. И здесь мы также можем использовать три варианта: public, protected
или private
.
Если спецификатор базового класса явным образом не указан:
class Employee: public Person
то по умолчанию применяется спецификатор private
(При наследовании структур, если спецификатор доступа не укзаан, то по умолчанию применяется спецификатор public
).
Таким образом, в базовом классе при определении переменных и функций мы можем использовать три спецификатора для управления доступом: public, protected
или private
.
И те же три спецификатора мы можем использовать при установке наследования от базового класса. Эти спецификаторы накладываются друг на друга и образуют 9 возможных комбинаций.
Если члены базового класса определены со спецификатором private, то в производном классе они в принципе недоступны независимо от спецификатора доступа к базовому классу.
Если спецификатор базового класса - public, то уровень доступа унаследованных членов остается неизменным.
Таким образом, унаследованные открытые члены являются общедоступными, а унаследованные члены со спецификатором protected
сохраняют этот спецификатор и в производном классе.
Если спецификатор базового класса - protected, то все унаследованные члены со спецификатором protected
и
public
в производном классе наследуются как protected
. Смысл этого состоит в том, что если у производного класса будут свои классы-наследники, то в этих классах-наследниках также можно обращаться
к подобным членам базового класса.
Если спецификатор базового класса - private, то все унаследованные члены со спецификатором protected
и
public
в производном классе наследуются как private
. Они доступны в любой функции производного класса, но вне производного класса (в том числе у его наследников) они не доступны.
Рассмотрим пример. Пусть спецификатором базового класса будет private
#include <iostream> class Person { public: Person(std::string name, unsigned age) { this->name = name; this->age = age; } void print() const { std::cout << "Name: " << name << "\tAge: " << age << std::endl; } protected: std::string name; // доступно из производных классов private: unsigned age; }; class Employee: private Person { public: Employee(std::string name, unsigned age, std::string company): Person(name, age) { this->company = company; } void printEmployee() const { print(); // функция print внутри класса Employee доступна std::cout << name << " works in " << company << std::endl; } private: std::string company; // компания }; int main() { Employee employee {"Bob", 42, "Microsoft"}; employee.printEmployee(); // Bob works in Microsoft // employee.print(); // функция print недоступна }
Поскольку спецификатор базового класса Person - private
, то класс Employee наследует переменную name
и функцию print
как private-члены.
К таким переменным и функциям можно обратиться внутри класса Employee. Однако вне класса Employee они будут недоступны:
Employee employee {"Bob", 42, "Microsoft"}; // employee.print(); // функция print недоступна
А если мы создадим новый класс и унаследуем его от Employee, например, класс Manager:
class Manager: public Employee { public: Manager(std::string name, unsigned age, std::string company): Employee(name, age, company) { } };
То приватные переменная name и функция print из Employee в классе Manager будут недоступны.
Что делать, если в примере выше для класса Employee мы все таки хотим вызвать функцию print? Мы можем восстановить уровень доступа с помощью ключевого слова using:
#include <iostream> class Person { public: Person(std::string name, unsigned age) { this->name = name; this->age = age; } void print() const { std::cout << "Name: " << name << "\tAge: " << age << std::endl; } protected: std::string name; // доступно из производных классов private: unsigned age; }; class Employee: private Person { public: Employee(std::string name, unsigned age, std::string company): Person(name, age) { this->company = company; } using Person::print; void printEmployee() const { std::cout << name << " works in " << company << std::endl; } private: std::string company; // компания }; int main() { Employee employee {"Bob", 42, "Microsoft"}; employee.print(); // Name: Bob Age: 42 - функция доступна }
В классе Employee мы устанавливаем уровень доступ к функции print базового класса Person как public:
using Person::print;
После этого функция print будет иметь свой первоначальный спецификатор доступа - public и будет доступна вне класса Employee:
Employee employee {"Bob", 42, "Microsoft"}; employee.print(); // Name: Bob Age: 42 - функция доступна
Подобным образом можно сделать публичной и переменную name
, несмотря на то, что в базовом классе Person она определена как protected
:
using Person::name;
Однако если переменные и функции определены в базовом классе как приватные сделать их публичными нельзя.