Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии 


Объявление доступа помещается в производном классе под соответствующим спецификатором доступа. Обратите внимание на то, что объявления типа в этом случае указывать не требуется.




Чтобы понять, как работает объявление доступа, рассмотрим сначала этот короткий фрагмент кода.

class base {

 public:

  int j; // public-доступ в классе base

};

// Класс base наследуется как private-класс.

class derived: private base {

 public:

  // Вот использование объявления доступа:

  base::j; // Теперь член j снова стал открытым.

// . . .

};

Поскольку класс base наследуется классом derived закрытым способом, его public-переменная j становится private-переменной класса derived. Однако включение этого объявления доступа

base::j;

в классе derived под спецификатором public восстанавливает public-статус члена j.

Объявление доступа можно использовать для восстановления прав доступа public- и protected-членов. Однако для изменения (повышения или понижения) статуса доступа его использовать нельзя. Например, член, объявленный закрытым в базовом классе, нельзя сделать открытым в производном. (Разрешение подобных вещей разрушило бы инкапсуляцию!)

Использование объявления доступа иллюстрируется в следующей программе.

#include <iostream>

using namespace std;

class base {

  int i; // private-член в классе base

 public:

  int j, k;

  void seti (int x) { i = x; }

  int geti() { return i; }

};

// Класс base наследуется как private-класс.

class derived: private base {

 public:

  /* Следующие три инструкции переопределяют private-наследование класса base и восстанавливают public-статус доступа для членов j, seti() и geti(). */

  base::j; // Переменная j становится снова public-членом, а переменная k остается закрытым членом.

  base::seti; // Функция seti() становится public-членом.

  base::geti; // Функция geti() становится public-членом.

  // base::i; // Неверно: нельзя повышать уровень доступа.

  int а; // public-член

};

Int main()

{

 derived ob;

 //ob.i = 10; // Неверно, поскольку член i закрыт в классе derived.

 ob.j = 20; // Допустимо, поскольку член j стал открытым в классе derived.

 //ob.k =30; // Неверно, поскольку член k закрыт в классе derived.

 ob.a = 40; // Допустимо, поскольку член а открыт в классе derived.

 ob.seti(10);

 cout << ob.geti() << " " << ob.j << " " << ob.a;

 return 0;

}

Обратите внимание на то, как в этой программе используются объявления доступа для восстановления статуса public у членов j, seti() и geti(). В комментариях отмечены и другие ограничения, связанные со статусом доступа.

C++ обеспечивает возможность восстановления уровня доступа для унаследованных членов, чтобы программист мог успешно программировать такие специальные ситуации, когда большая часть наследуемого класса должна стать закрытой, а прежний public-или protected-статус нужно вернуть лишь нескольким членам. И все же к этому средству лучше прибегать только в крайних случаях.

Чтение С++-графов наследования

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

Как видите, стрелки на этом рисунке направлены вверх, а не вниз. Многие поначалу считают такое направление стрелок алогичным, но именно этот стиль принят большинством С++-программистов. Согласно стилевой графике C++ стрелка должна указывать на базовый класс. Следовательно, стрелка означает "выведен из", а не "порождает". Рассмотрим другой пример. Можете ли вы описать словами значение следующего изображения?

Из этого графа следует, что класс Е выведен из обоих классов С и D. (Другими словами, класс Е имеет два базовых класса С и D.) При этом класс С выведен из класса А, а класс D — из класса В. Несмотря на то что направление стрелок может вас обескураживать на первых порах, все же лучше познакомиться с этим стилем графических обозначений, поскольку он широко используется в книгах, журналах и документации на компиляторы.

Виртуальные базовые классы

При наследовании нескольких базовых классов в С++-программу может быть внесен элемент неопределенности. Рассмотрим эту некорректную программу.

/* Эта программа содержит ошибку и не скомпилируется.

*/

#include <iostream>

using namespace std;

class base {

 public:

  int i;

};

// Класс derived1 наследует класс base.

class derived1 : public base { public: int j;};

// Класс derived2 наследует класс base.

class derived2 : public base { public: int k;};

/* Класс derived3 наследует оба класса derived1 и derived2. Это означает, что в классе derived3 существует две копии класса base!

*/

class derived3 : public derived1, public derived2 {

 public:

  int sum;

};

Int main()

{

 derived3 ob;

 ob.i = 10; // Это и есть неоднозначность: какой именно член i имеется в виду???

 ob.j = 20;

 ob.k = 30;

 //И здесь тоже неоднозначность с членом i.

 ob.sum = ob.i + ob.j + ob.k;

 // И здесь тоже неоднозначность с членом i.

 cout << ob.i << " ";

 cout << ob. j << " " << ob.k << " ";

 cout << ob.sum;

 return 0;

}

Как отмечено в комментариях этой программы, оба класса derived1 и derived2 наследуют класс base. Но класс derived3 наследует как класс derived1, так и класс derived2. В результате в объекте типа derived3 присутствуют две копии класса base, поэтому, например, в таком выражении

ob.i = 20;

не ясно, на какую именно копию члена i здесь дана ссылка: на член, унаследованный от класса derived1 или от класса derived2? Поскольку в объекте ob присутствуют обе копии класса base, то в нем существуют и два члена ob.is! Потому-то эта инструкция и является наследственно неоднозначной (существенно неопределенной).

Есть два способа исправить предыдущую программу. Первый состоит в применении оператора разрешения контекста (разрешения области видимости), с помощью которого можно "вручную" указать нужный член i. Например, следующая версия этой программы успешно скомпилируется и выполнится ожидаемым образом.

/* Эта программа использует оператор разрешения контекста для выбора нужного члена i.

*/

#include <iostream>

using namespace std;

class base {

 public:

  int i;

};

// Класс derived1 наследует класс base.

class derived1 : public base { public: int j;};

// Класс derived2 наследует класс base.

class derived2 : public base { public: int k;};

/* Класс derived3 наследует оба класса derived1 и derived2. Это означает, что в классе derived3 существует две копии класса base!

*/

class derived3 : public derived1, public derived2 {

 public:

  int sum;

};

Int main()

{

 derived3 ob;

 ob.derived1::i = 10; // Контекст разрешен, используется член i класса derived1.

 ob.j = 20;

 ob.k = 30;

 // Контекст разрешен и здесь.

 ob.sum = ob.derived1::i + ob.j + ob.k;

 // Неоднозначность ликвидирована и здесь.

 cout << ob.derived1::i << " ";

 cout << ob.j << " " << ob.k << " ";

 cout << ob.sum;

 return 0;

}

Виртуальное наследование базового класса гарантирует, что в любом производном классе будет присутствовать только одна его копия.

Применение оператора "::" позволяет программе "ручным способом" выбрать версию класса base (унаследованную классом derived1). Но после такого решения возникает интересный вопрос: а что, если в действительности нужна только одна копия класса base? Можно ли каким-то образом предотвратить включение двух копий в класс derived3? Ответ, как, наверное, вы догадались, положителен. Это решение достигается с помощью виртуальных базовых классов.

Если два (или больше) класса выведены из общего базового класса, мы можем предотвратить включение нескольких его копий в объекте, выведенном из этих классов, что реализуется путем объявления базового класса при его наследовании виртуальным. Для этого достаточно предварить имя наследуемого базового класса ключевым словом virtual.

Для иллюстрации этого процесса приведем еще одну версию предыдущей программы. На этот раз класс derived3 содержит только одну копию класса base.

// Эта программа использует виртуальные базовые классы.

#include <iostream>

using namespace std;

class base {

 public:

 int i;

};

// Класс derived1 наследует класс base как виртуальный.

class derived1 : virtual public base { public: int j;};

// Класс derived2 наследует класс base как виртуальный.

class derived2 : virtual public base { public: int k;};

/* Класс derived3 наследует оба класса derived1 и derived2. На этот раз он содержит только одну копию класса base.

*/

class derived3 : public derived1, public derived2 {

 public:

  int sum;

};

Int main()

{

 derived3 ob;

 ob.i = 10; // Теперь неоднозначности нет.

 ob.j = 20;

 ob.k = 30;

 // Теперь неоднозначности нет.

 ob.sum = ob.i + ob.j + ob.k;

 // Теперь неоднозначности, нет.

 cout << ob.i << " ";

 cout << ob.j << " " << ob.k << " ";

 cout << ob.sum;

 return 0;

}

Как видите, ключевое слово virtual предваряет остальную часть спецификации наследуемого класса. Теперь оба класса derived1 и derived2 наследуют класс base как виртуальный, и поэтому при любом множественном их наследовании в производный класс будет включена только одна его копия. Следовательно, в классе derived3 присутствует лишь одна копия класса base, а инструкция ob.i = 10 теперь совершенно допустима и не содержит никакой неоднозначности.

И еще. Даже если оба класса derived1 и derived2 задают класс base как virtual-класс, он по-прежнему присутствует в объекте любого типа. Например, следующая последовательность инструкций вполне допустима.

// Определяем класс типа derived1.

derived1 myclass;

myclass.i = 88;

Разница между обычным базовым и виртуальным классами становится очевидной только тогда, когда этот базовый класс наследуется более одного раза. Если базовый класс объявляется виртуальным, то только один его экземпляр будет включен в объект наследующего класса. В противном случае в этом объекте будет присутствовать несколько его копий.





Читайте также:

  1. D. НОВЫЕ ТЕХНОЛОГИИ ДЛЯ ОБЕСПЕЧЕНИЯ ХРАНЕНИЯ И ДОСТУПА К ИНФОРМЦИИ О ПРОМЫШЛЕННОЙ СОБСТВЕННОСТИ
  2. D. Правоспособность иностранцев. - Ограничения в отношении землевладения. - Двоякий смысл своего и чужого в немецкой терминологии. - Приобретение прав гражданства русскими подданными в Финляндии
  3. F06.22 Бредовое (шизофреноподобное) расстройство в связи с эпилепсией
  4. F9 Эмоциональные расстройства и расстройства поведения, начинающиеся обычно в детском и подростковом возрасте.
  5. G. ГОСУДАРСТВЕННАЯ ПОДДЕРЖКА ИЗОБРЕТАТЕЛЬСКОЙ ДЕЯТЕЛЬНОСТИ
  6. I. АНАЛИЗ И ПОДГОТОВКА ПРОДОЛЬНОГО ПРОФИЛЯ ПУТИ ДЛЯ ВЫПОЛНЕНИЯ ТЯГОВЫХ РАСЧЕТОВ
  7. I. Слова на праздники Господни и Богородичные
  8. II. Однородные члены предложения могут отделяться от обобщающего слова знаком тире (вместо обычного в таком случае двоеточия), если они выполняют функцию приложения со значением уточнения.
  9. II. Проверка и устранение затираний подвижной системы РМ.
  10. III. Проверка полномочий лица, подписывающего договор
  11. IV. Рассказ преподавателя по теме: «Витагенный опыт ребенка».
  12. IV.1. Общая дифференциация и типология детско-подростковой дезадаптации.


Последнее изменение этой страницы: 2016-03-17; Просмотров: 230; Нарушение авторского права страницы


lektsia.com 2007 - 2018 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.026 с.) Главная | Обратная связь