Перейти к содержимому

Как наследовать private в c

  • автор:

C++. Наследование. Общие понятия. Использование модификаторов private, protected, public

Наследование. Общие понятия. Использование модификаторов private , protected , public при наследовании

Поиск на других ресурсах:

1. Повторное использование кода. Наследование

Идея наследования в программировании взята из природы и начинается еще с 60-х годов 20 века. В языке C++ концепция классов существенно усиливается благодаря внедрению наследованию в классах. В природе наследование позволяет добавлять к родительским качествам новые навыки. В программировании наследование – это свойство класса получать программный код (навыки) другого (базового) класса, добавляя к нему свой собственный код, тем самым расширяя его возможности.

С помощью механизма наследования можно без ограничений изменять любой класс, разработанный собственно программистом или другими программистами. При этом не следует перестраивать структуру этого класса. В базовый класс добавляются новые возможности. Возможности базового класса в унаследованном классе могут быть расширены, изменены, сужены, уничтожены или оставлены без изменений.

Наследование свойственно только классам и их характеристикам, а не переменным или функциям.

Проекты на языке C++ базируются на использовании конкретных классов, решающих поставленные задачи. Благодаря использованию наследования классы строятся постепенно, от базовых простых классов до специализированных классов, постепенно детализирующих решение. В итоге классы образуют иерархию классов. В этих иерархиях классы верхних уровней (базовые классы) описывают некоторые общие для всех характеристики, которые на нижних уровнях детализируются в унаследованных классах. Завершенный программный проект есть работоспособной системой, состоящей из иерархически связанных между собой классов. Количество классов в некоторых проектах может составлять десятки или сотни.

В отличие от некоторых языков программирования, в языке C++ разрешено множественное наследование. При множественном наследовании один класс может быть унаследован из нескольких базовых классов, получая их свойства и поведение.

В контексте понятия наследования определяется понятие повторного использования кода, которое в программировании на C++ определено двумя аспектами:

  • создание класса с целью получения экземпляров;
  • создание класса для его использования в качестве базового, передающего свои характеристики унаследованным классам.

Порождение кода от базового класса является эффективным способом использования уже написанного кода для собственных нужд. Наследование является одной из частей повторного использования кода.

2. Синтаксис объявления классов, образующих иерархию. Базовый класс и производный (унаследованный) класс

Для реализации наследования требуется наличие как минимум двух классов. Если в программе класс с именем B должен быть унаследован от класса с именем A , то объявление классов выглядит следующим образом:

class A < // Элементы класса A // . >; class B : A < // Составляющие элементы класса B // . >;

В вышеприведенном объявлении класс B наследует часть характеристик или все характеристики класса A . Из класса B может быть унаследован другой (третий) класс, который получит часть или все характеристики классов A и B . Кроме того, из класса A могут быть унаследованы один или несколько классов, которые по отношению к классу B образуют параллельную ветвь иерархии. На рисунке 1 представлен один из возможных вариантов образования классами древовидной иерархии наследования.

C++. Наследование. Дерево наследования, созданное классами

Рисунок 1. Наследование. Дерево наследования, созданное классами

3. Передача характеристик наследуемому классу. Варианты реализации. Ограничение и расширение доступа. Ключевые слова private , protected , public

Важным при наследовании является вопрос: как передать характеристики базового класса в унаследованный класс? Здесь C++ дает широкий спектр возможных вариантов реализации.

Для указания того, какие элементы базового класса должны быть доступны в унаследованном классе, используется механизм инкапсуляции. Основой этого механизма является использование ключевых слов private , protected , public для предоставления доступа или запрета доступа к элементам базового класса.

3.1. Взаимодействие двух классов. Доступ из унаследованного класса к элементам базового класса

Элемент базового класса (переменная, функция) может быть объявлен с одним из трех модификаторов доступа:

  • private (скрытый). В этом случае доступа к этому элементу из унаследованного класса нет;
  • protected (защищенный) – позволяет использовать элемент базового класса в унаследованном классе;
  • public (общедоступный) – в унаследованном классе действует так же, как protected .

На рисунке 2 показаны все 3 вида доступа для двух классов, образующих иерархию наследования. Демонстрируется доступ к переменной a базового класса A из функции Func() унаследованного класса B . Те же правила действуют не только для переменных, но и для функций базового класса A .

C++. Наследование для двух классов A и B

Рисунок 2. Наследование для двух классов A и B . К private -членам базового класса доступ из унаследованного класса запрещен. К protected — и public — членам базового класса доступ разрешен

3.2. Взаимодействие двух классов. Доступ из экземпляра (объекта) унаследованного класса к элементам базового класса с использованием модификаторов доступа private и protected

Если рассматривать доступ из экземпляра (объекта) унаследованного класса к элементам базового класса, то отличие от предыдущего случая (пункт 3.1) заключается в использовании ключевого слова protected и способе наследования класса. Класс может быть унаследован как private , protected или public . Если класс унаследован как private , то ключевое слово private указывать не обязательно (см. рисунок 2).

На рисунке 3 изображены возможные варианты доступа к элементам базового класса из экземпляра унаследованного класса. В любом варианте наследования базового класса доступ к элементам этого класса из экземпляра производного класса запрещен.

C++. Наследование. Модификаторы доступа private, protected запрещают доступ

Рисунок 3. Модификаторы доступа private , protected запрещают доступ к элементам класса из любого экземпляра класса

3.3. Взаимодействие двух классов. Доступ к элементам базового класса из производного класса. Модификатор доступа public

Если в базовом классе A некий элемент объявляется с модификатором доступа public , то экземпляр унаследованного класса B :

  • имеет доступ к элементам базового класса A , если производный класс B наследует класс A с модификатором доступа public ;
  • не имеет доступа к элементам базового класса A , если производный класс B наследует класс A как private — или protected -.

На рисунке 4 наглядно показаны все возможные варианты такого доступа.

C++. Наследование. Доступ к public-элементам базового класса

Рисунок 4. Доступ к public -элементам базового класса: 1) базовый класс унаследован как private -класс; 2) базовый класс унаследован как protected -класс; 3) базовый класс унаследован как public -класс.

4. Наследование 3-х и более классов. Доступ из унаследованных классов

Как было показано выше, базовый класс может быть унаследован путём указания перед его именем модификаторов private , protected , public . Отличие в использовании этих модификаторов проявляется в классе, являющемся производным от унаследованного класса. То есть, если дано три класса с именами A , B , C , последовательно наследующими друг друга, то влияние модификаторов будет именно на класс C .

4.1. Доступ к private -базовому классу из производных классов

На рисунке 5 изображен доступ к базовому классу A из производного класса C . Класс A в классе B наследуется как private -класс (скрытый). Это означает, что доступ к любому элементу класса A из производного класса C запрещен.

C++. Наследование. Запрещен доступ к элементам private-класса A из унаследованного класса C

Рисунок 5. Запрещен доступ к элементам private -класса A из унаследованного класса C

4.2. Доступ к protected — и public — базовому классу из производных классов

Если базовый класс A унаследован с модификаторами protected или public , то элементы этого класса доступны в унаследованных классах B и C .

C++. Наследование. Модификаторы protected и public перед именем базового класса

Рисунок 6. Модификаторы protected и public перед именем базового класса. Открытие доступа к protected — и public -элементам базового класса

Связанные темы

  • Порядок вызова конструкторов при наследовании. Ограничения наследования. Свойства указателя на базовый класс
  • Полиморфизм. Виртуальные функции. Общие понятия. Спецификаторы virtual , override . Примеры
  • Абстрактный класс. Чисто виртуальная функция. Примеры

Как наследовать private в c

Наследование (inheritance) представляет один из ключевых аспектов объектно-ориентированного программирования, который позволяет наследовать функциональность одного класса (базового класса) в другом — производном классе (derived class).

Зачем нужно наследование? Рассмотрим небольшую ситуацию, допустим, у нас есть классы, которые представляют человека и сотрудника компании:

class Person < public: void print() const < std::cout std::string name; // имя unsigned age; // возраст >; class Employee < public: void print() const < std::cout std::string name; // имя unsigned age; // возраст std::string company; // компания >;

В данном случае класс Employee фактически содержит функционал класса Person: свойства name и age и функцию print. В целях демонстрации все переменные здесь определены как рубличные. И здесь, с одной стороны, мы сталкиваемся с повторением функционала в двух классах. С другой строны, мы также сталкиваемся с отношением is («является»). То есть мы можем сказать, что сотрудник компании ЯВЛЯЕТСЯ человеком. Так как сотрудник компании имеет в принципе все те же признаки, что и человек (имя, возраст), а также добавляет какие-то свои (компанию). Поэтому в этом случае лучше использовать механизм наследования. Унаследуем класс Employee от класса Person:

class Person < public: void print() const < std::cout std::string name; // имя unsigned age; // возраст >; class Employee : public Person < public: std::string company; // компания >;

Для установки отношения наследования после названия класса ставится двоеточие, затем идет спецификатор доступа и название класса, от которого мы хотим унаследовать функциональность. В этом отношении класс Person еще будет называться базовым классом (также называют суперклассом, родительским классом), а Employee — производным классом (также называют подклассом, классом-наследником).

Спецификатор доступа позволяет указать, к каким членам класса производный класс будет иметь доступ. В данном случае используется спецификатор public , который позволяет использовать в производном классе все публичные члены базового класса. Если мы не используем модификатор доступа, то класс Employee ничего не будет знать о переменных name и age и функции print.

После установки наследования мы можем убрать из класса Employee те переменные, которые уже определены в классе Person. Используем оба класса:

#include class Person < public: void print() const < std::cout std::string name; // имя unsigned age; // возраст >; class Employee : public Person < public: std::string company; // компания >; int main() < Person tom; tom.name = "Tom"; tom.age = 23; tom.print(); // Name: Tom Age: 23 Employee bob; bob.name = "Bob"; bob.age = 31; bob.company = "Microsoft"; bob.print(); // Name: Bob Age: 31 >

Таким образом, через переменную класса Employee мы можем обращаться ко всем открытым членам класса Person.

Конструкторы

Но теперь сделаем все переменные приватными, а для их инициализации добавим конструкторы. И тут стоит учитывать, что конструкторы при наследовании не наследуются . И если базовый класс содержит только конструкторы с параметрами, то производный класс должен вызывать в своем конструкторе один из конструкторов базового класса:

#include class Person < public: Person(std::string name, unsigned age) < this->name = name; this->age = age; > void print() const < std::cout 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; > private: std::string company; // компания >; int main() < Person person ; person.print(); // Name: Tom Age: 38 Employee employee ; employee.print(); // Name: Bob Age: 42 >

После списка параметров конструктора производного класса через двоеточие идет вызов конструктора базового класса, в который передаются значения параметров n и a.

Employee(std::string name, unsigned age, std::string company): Person(name, age) < this->company = company; >

Если бы мы не вызвали конструктор базового класса, то это было бы ошибкой.

Консольный вывод программы:

Name: Tom Age: 38 Name: Bob Age: 42

Таким образом, в строке

Employee employee ;

Вначале будет вызываться конструктор базового класса Person, в который будут передаваться значения «Bob» и 42. И таким образом будут установлены имя и возраст. Затем будет выполняться собственно конструктор Employee, который установит компанию.

Также мы могли бы определить конструктор Employee следующим образом, используя списки инициализации:

Employee(std::string name, unsigned age, std::string company): Person(name, age), company(company)

Подключение конструктора базового класса

В примерах выше конструктор Employee отличается от конструктора Person одним параметром — company. Все остальные параметры из Employee передаются в Person. Однако, если бы у нас было бы полное соответствие по параметрам между двумя классами, то мы могли бы и не определять отдельный конструктор для Employee, а подключить конструктор базового класса:

#include class Person < public: Person(std::string name, unsigned age) < this->name = name; this->age = age; > void print() const < std::cout private: std::string name; // имя unsigned age; // возраст >; class Employee: public Person < public: using Person::Person; // подключаем конструктор базового класса >; int main() < Person person ; person.print(); // Name: Tom Age: 38 Employee employee ; employee.print(); // Name: Bob Age: 42 >

Здесь в классе Employee подключаем конструктор базового класса с помощью ключевого слова using :

using Person::Person;

Таким образом, класс Employee фактически будет иметь тот же конструктор, что и Person с теми же двумя параметрами. И этот конструктор мы также можем вызвать для создания объекта Employee:

Employee employee ;

Определение конструкторов копирования

При определении конструктора копирования в производном классе следует вызывать в нем конструктор копирования базового класса. Например, добавим в классы Person и Employee конструкторы копирования:

#include class Person < public: // конструктор копирования класса Person Person(const Person& person) < name = person.name; age = person.age; >Person(std::string name, unsigned age) < this->name = name; this->age = age; > void print() const < std::cout 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; > // конструктор копирования класса Employee // вызываем конструктор копирования базового класса Employee(const Employee& employee): Person(employee) < company=employee.company; >private: std::string company; >; int main() < Employee tom; Employee tomas; // вызываем конструктор копирования tomas.print(); // Name: Tom Age: 38 >

В конструкторе копирования производного класса Employee вызываем конструктор копирования базового класса Person:

Employee(const Employee& employee): Person(employee)

При этом в конструктор копирования Person передается объект employee, где будут установлены переменные name и age. В самом же конструкторе класса Employee лишь устанавливается переменная company.

Наследование деструкторов

Уничтожение объекта производного класса может вовлекать как собственно деструктор производного класса, так и деструктор базового класса. Например, определим в обоих классах деструкторы

#include class Person < public: Person(std::string name, unsigned age) < this->name = name; this->age = age; std::cout ~Person() < std::cout void print() const < std::cout 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; std::cout ~Employee() < std::cout private: std::string company; >; int main() < Employee tom; tom.print(); >

В обоих классах деструктор просто выводит некоторое сообщение. В функции main создается один объект Employee, однако при завершении программы будет вызываться деструктор как из производного, так и из базового класса:

Person created Employee created Name: Tom Age: 38 Employee deleted Person deleted

По консольному выводу мы видим, что при создании объекта Employee сначала вызывается конструктор базового класса Person и затем собственно конструктор Employee. А при удалении объекта Employee процесс идет в обратном порядке — сначала вызывается деструктор производного класса и затем деструктор базового класса. Соответственно, если в деструкторе базового класса идет освобождение памяти, то оно в любом случае будет выполнено при удалении объекта производного класса.

Запрет наследования

Иногда наследование от класса может быть нежелательно. И с помощью спецификатора final мы можем запретить наследование:

class Person final < >;

После этого мы не сможем унаследовать другие классы от класса User. И, например, если мы попробуем написать, как в случае ниже, то мы столкнемся с ошибкой:

class Employee : public Person < >;

Private наследование

Для чего нужно наследование с описателем доступа private, если все поля базового класса станут недоступными?

Отслеживать
1,342 3 3 золотых знака 14 14 серебряных знаков 27 27 бронзовых знаков
задан 16 янв 2016 в 11:06
161 1 1 серебряный знак 3 3 бронзовых знака
Если вам дан исчерпывающий ответ, отметьте его как верный (галка напротив выбранного ответа).
17 янв 2016 в 8:37

3 ответа 3

Сортировка: Сброс на вариант по умолчанию

Именно для того, чтобы все члены базового класса не были доступны снаружи (были доступны только изнутри класса, или друзьям класса).

Наследование мало чем отличается от аггрегации, и
class A : private B < . >; аналогично
class A < private: B b; >; .

Одна в отличие от private: B b; , при наследовании функции B можно вызывать как члены своего класса, т.е. не b.f() , а просто f() .

Также можно унаследоваться от абстрактного класса, и переопределить его методы, не показывая все эти детали наружу, например:

struct Callback < virtual void done() = 0; >; void run(Callback* b); class Cls : private Callback < public: void some() < run(this); >private: void done() override < . >>; 

Отслеживать
207k 28 28 золотых знаков 293 293 серебряных знака 526 526 бронзовых знаков
ответ дан 16 янв 2016 в 11:11
31k 13 13 золотых знаков 96 96 серебряных знаков 157 157 бронзовых знаков
А зачем такое вообще может пригодится? Ну кроме потому что можно?
16 янв 2016 в 11:12

@Flownee Чтоб приватные члены базового класса остались в производном так же скрытыми от остальных, чтоб не получились публичные члены класса (что всегда подозрительно)

16 янв 2016 в 11:18
в примере Callback не должен был зваться Base ?
16 янв 2016 в 11:22

@Flowneee, это наследование функционала без отношения «is», т.е. после такого наследования класс наследник не может быть преобразован к родителю.

16 янв 2016 в 12:08

@Flowneee, как и почти все в ООП для того, чтобы автору было приятно писать, а остальным (при исправлении багов) было сложнее быстро и точно понять, а почему же вся эта фигня не работает.

16 янв 2016 в 13:18

Приватное наследование — это по существу композиция, выраженная немного по-другому. При композиции у вас вложенный объект доступен по своему имени, но не виден снаружи. При приватном наследовании вложенный объект «влит» в *this .

Вообще, роль наследования — выражать отношение Is-A между классами. То есть, выражать наследование интерфейса, при этом наследование имплементации есть техническая деталь. А приватное наследование именно это и не может, при приватном наследовании наследуется именно имплементация, но не интерфейс.

Поэтому практически всегда вместо приватного наследования стоит предпочесть именно композицию: она выражает ту же идею, но явно.

Отслеживать
ответ дан 16 янв 2016 в 12:15
207k 28 28 золотых знаков 293 293 серебряных знака 526 526 бронзовых знаков

Читал ваш ответ, и в голову пришла мысль — возможно использование приватного наследования будет эффективнее по производительности чем явное использование агрегации (меньше потребления памяти) + меньше инструкций на вызов сокрытых методов

Наследование. Закрытое/частное (private) и защищенное (protected) наследование / FAQ C++

Закрытое наследование – это синтаксический вариант композиции (она же агрегация и/или связь типа «имеет»).

Например, связь « Car (автомобиль) имеет Engine (двигатель)» можно выразить с помощью простой композиции:

class Engine < public: Engine(int numCylinders); void start(); // запускает этот "двигатель" >; class Car < public: Car() : e_(8) < >// Инициализирует автомобиль (Car) с 8 цилиндрами void start() < e_.start(); >// Запускает автомобиль (Car), запуская его двигатель (Engine) private: Engine e_; // Автомобиль (Car) имеет двигатель (Engine) >;

Связь « Car имеет Engine » также можно выразить с помощью закрытого (частного) наследования:

class Car : private Engine < // Автомобиль (Car) имеет двигатель (Engine) public: Car() : Engine(8) < >// Инициализирует автомобиль (Car) с 8 цилиндрами using Engine::start; // Запускает автомобиль (Car), запуская его двигатель (Engine) >;

Между этими двумя вариантами есть несколько общих черт:

  • в обоих случаях в каждом объекте Car содержится ровно один объект-член Engine ;
  • ни в том, ни в другом случае пользователи (посторонние) не могут преобразовать Car* в Engine* ;
  • в обоих случаях класс Car имеет метод start() , который вызывает метод start() содержащегося в нем объекта Engine .

Также есть несколько отличий:

  • вариант простой композиции нужен, если вы хотите иметь в Car несколько объектов Engine ;
  • вариант с частным наследованием может ввести ненужное множественное наследование;
  • вариант с частным наследованием позволяет членам Car преобразовывать Car* в Engine* ;
  • вариант с частным наследованием позволяет получить доступ к защищенным членам базового класса;
  • вариант с частным наследованием позволяет Car переопределять виртуальные функции Engine ;
  • вариант с частным наследованием немного упрощает (20 символов по сравнению с 28 символами) предоставление Car метода start() , который просто вызывает метод start() класса Engine .

Обратите внимание, что частное наследование обычно используется для получения доступа к защищенным членам базового класса, но обычно это краткосрочное решение (перевод: пластырь).

Что предпочесть: композицию или частное наследование?

Используйте композицию, когда можете, а частное наследование, когда вам нужно.

Обычно вы не хотите иметь доступ к внутренним компонентам слишком многих других классов, а частное наследование дает вам некоторые из этих дополнительных полномочий (и ответственностей). Но частное наследование – это не зло; просто его дороже поддерживать, так как оно увеличивает вероятность того, что кто-то изменит что-то, что нарушит ваш код.

Легальное, долгосрочное использование частного наследования – это когда вы хотите создать класс Fred , который использует код в классе Wilma , а код из класса Wilma должен вызывать функции-члены из вашего нового класса Fred . В этом случае Fred вызывает невиртуальные функции в Wilma , а Wilma вызывает в себе (обычно чисто) виртуальные функции, которые перегружены классом Fred . Сделать это с помощью композиции будет намного сложнее.

class Wilma < protected: void fredCallsWilma() < std::cout virtual void wilmaCallsFred() = 0; // чисто виртуальная функция >; class Fred : private Wilma < public: void barney() < std::cout protected: virtual void wilmaCallsFred() < std::cout >;

Должен ли я приводить указатель из производного класса с частным наследованием к его базовому классу?

Как правило, нет.

Об отношении к базовому классу из функции-члена или друга производного класса с частным наследованием известно, и восходящее преобразование из PrivatelyDer* в Base* (или PrivatelyDer& в Base& ) безопасно; приведение типов не требуется и не рекомендуется.

Однако пользователям PrivatelyDer следует избегать этого небезопасного преобразования, поскольку оно основано на закрытом/частном решении PrivatelyDer и может быть изменено без предварительного уведомления.

Как защищенное ( protected ) наследование связано с частным ( private ) наследованием?

Сходства: оба позволяют переопределять виртуальные функции в частном/защищенном базовом классе, и ни одно из них не утверждает, что производный класс является разновидностью базового класса.

Различия: защищенное наследование позволяет производным классам производных классов знать о наследственной связи. Таким образом, ваши внуки действительно узнают о деталях вашей реализации. Это имеет как преимущества (позволяет производным классам защищенного производного класса использовать связь с защищенным базовым классом), так и затраты (защищенный производный класс не может изменить связи, не нарушив при этом последующие производные классы).

Защищенное наследование использует синтаксис : protected .

class Car : protected Engine < public: // . >;

Каковы правила доступа при частном и защищенном наследованиях?

Возьмем в качестве примера следующие классы:

class B < /*. */ >; class D_priv : private B < /*. */ >; class D_prot : protected B < /*. */ >; class D_publ : public B < /*. */ >; class UserClass < B b; /*. */ >;

Ни один из производных классов не может получить доступ к чему-либо, что является private в B . В D_priv открытая и защищенная части B являются частными. В D_prot открытая и защищенная части B являются защищенными. В D_publ открытые части B являются открытыми, а защищенные части B – защищенными ( D_publ – это разновидность B ). Класс UserClass может получить доступ только к открытым частям B , что «изолирует» UserClass от B .

Чтобы сделать публичный член B публичным в D_priv или D_prot , укажите имя члена с префиксом B:: . Например, чтобы сделать член B::f(int, float) открытым в D_prot , вы должны сказать:

class D_prot : protected B < public: using B::f; // Примечание: не using B::f(int,float) >;

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *