|
|||||||
АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомДругоеЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция |
Абстрактные классы и чистые виртуальные функции. Виртуальные деструкторы. Дружественные функции. Дружественные классыАбстрактные классы и чистые виртуальные функции Базовый класс, объекты которого никогда не будут реализованы, называется абстрактным классом. Такой класс может существовать с единственной целью — быть родительским по отношению к производным классам, объекты которых будут реализованы. Еще он может служить звеном для создания иерархической структуры классов. Как нам объяснить людям, использующим созданную нами структуру классов, что объекты родительского класса не предназначены для реализации? Можно, конечно, заявить об этом в документации, но это никак не защитит наш базозый класс от использования не по назначению. Надо защитить его программно. Для этого достаточно ввести в класс хотя бы одну чистую виртуальную функцию. Чистая виртуальная функция — это функция, после объявления которой добавлено выражение =0. Продемонстрируем сказанное в примере VIRTPURE: // virtpure.cpp // Чистая виртуальная функция #include <iostream> using namespace std; class Base //базовый класс { public: virtual void show() = 0; //чистая виртуальная функция }; class Derv1: public Base //порожденный класс 1 { public: void show() { cout << "Derv1\n"; } }; ////////////////////////////////////////////////// class Derv2: public Base //порожденный класс 2 { public: void show() { cout << "Derv2\n"; } }; ///////////////////////////////////////////////// int main() { // Base bad; //невозможно создать объект //из абстрактного класса Base* arr[2]; //массив указателей на //базовый класс Derv1 dv1; //Объект производного класса 1 Derv2 dv2; //Объект производного класса 2
arr[0] = &dv1; //Занести адрес dv1 в массив arr[1] = &dv2; //Занести адрес dv2 в массив
arr[0]->show(); //Выполнить функцию show() arr[1]->show(); //над обоими объектами return 0; } Здесь виртуальная функция show() объявляется так: virtual void show() = 0; //чистая виртуальная функция Знак равенства не имеет ничего общего с операцией присваивания. Нулевое значение ничему не присваивается. Конструкция =0 — это просто способ сообщить компилятору, что функция будет чистой виртуальной. Если теперь в main() попытаться создать объект класса Base, то компилятор будет недоволен тем, что объект абстрактного класса пытаются реализовать. Он выдаст даже имя чистой виртуальной функции, которая делает класс абстрактным. Помните, что хотя это только объявление, но определение функции show() базового класса не является обязательным. Впрочем, если вам надо его написать, это можно сделать. Как только в базовом классе окажется чистая виртуальная функция, необходимо будет позаботиться о том, чтобы избежать ее употребления во всех производных классах, которые вы собираетесь реализовать. Если класс использует чистую виртуальную функцию, он сам становится абстрактным, никакие объекты из него реализовать не удастся (производные от него классы, впрочем, уже не имеют этого ограничения). Более из эстетических соображений, нежели из каких-либо иных, можно все виртуальные функции базового класса сделать чистыми. Между прочим, мы внесли еще одно, не связанное с предыдущими, изменение в VIRTPURE: теперь адреса методов хранятся в массиве указателей и доступны как элементы этого массива. Обработка этого, впрочем, ничем не отличается от использования единственного указателя. VIRTPURE выдает результат, не отличающийся от VIRT: Derv1 Derv2
Виртуальные деструкторы Знаете ли вы, что деструкторы базового класса обязательно должны быть виртуальными? Допустим, чтобы удалить объект порожденного класса, вы выполнили delete над указателем базового класса, указывающим на порожденный класс. Если деструктор базового класса не является виртуальным, тогда delete, будучи обычным методом, вызовет деструктор для базового класса вместо того, чтобы запустить деструктор для порожденного класса. Это приведет к тому, что будет удалена только та часть объекта, которая относится к базовому классу. Программа VIRTDEST демонстрирует это. //vertdest.cpp //Тест невиртуальных и виртуальных деструкторов #include <iostream> using namespace std; /////////////////////////////////////////////////////////// class Base { public: ~Base() //невиртуальный деструктор // virtual ~Base() //виртуальный деструктор { cout << "Base удален\n"; } }; /////////////////////////////////////////////////////////// class Derv: public Base { public: ~Derv() { cout << "Derv удален\n"; } }; /////////////////////////////////////////////////////////// int main() { Base* pBase = new Derv; delete pBase; return 0; } Программа выдает такой результат: Base удален Это говорит о том, что деструктор для Derv не вызывается вообще! К такому результату привело то, что деструктор базового класса в приведенном листинге невиртуальный. Исправить это можно, закомментировав первую строчку определения деструктора и активизировав вторую. Теперь результатом работы программы является:
Derv удален Base удален
Только теперь обе части объекта порожденного класса удалены корректно. Конечно, если ни один из деструкторов ничего особенно важного не делает (например, просто освобождает память, занятую с помощью new), тогда их виртуальность перестает быть такой уж необходимой. Но в общем случае, чтобы быть уверенным в том, что объекты порожденных классов удаляются так, как нужно, следует всегда делать деструкторы в базовых классах виртуальными. Большинство библиотек классов имеют базовый класс, в котором есть виртуальный деструктор, что гарантирует нам наличие виртуальных деструкторов в порожденных классах. Дружественные функции Принцип инкапсуляции и ограничения доступа к данным запрещает функциям, не являющимся методами соответствующего класса, доступ к скрытым (private) или защищенным данным объекта. Политика этих принципов ООП такова, что, если функция не является членом объекта, она не может пользоваться определенным рядом данных. Тем не менее есть ситуации, когда такая жесткая дискриминация приводит к значительным неудобствам. Представьте, что вам необходимо, чтобы функция работала с объектами двух разных классов. Например, функция будет рассматривать объекты двух классов как аргументы и обрабатывать их скрытые данные. В такой ситуации ничто не спасет, кроме friend-функции. Приведем простой пример FRIEND, дающий представление о работе дружественных функций в качестве мостов между двумя классами. // friend.cpp // Дружественные функции #include <iostream> using namespace std; /////////////////////////////////////////////////////////// class beta; //нужно для объявления frifunc
class alpha { private: int data; public: alpha(): data(3) { } //конструктор без //аргументов friend int frifunc(alpha, beta); //дружественная //функция }; /////////////////////////////////////////////////////////// class beta { private: int data; public: beta(): data(7) { } //конструктор без //аргументов friend int frifunc(alpha, beta); //дружественная //функция }; ///////////////////////////////////////////////////////////
Int frifunc(alpha a, beta b) //определение функции { return(a.data + b.data); } //--------------------------------------------------------- int main() { alpha aa; beta bb;
cout << frifunc(aa, bb) << endl; //вызов функции return 0; }
В этой программе мы видим два класса — alpha и beta. Конструкторы этих классов задают их единственные элементы данных в виде фиксированных значений (3 и 7 соответственно).Нам необходимо, чтобы функция frifunc() имела доступ и к тем, и к другим скрытым данным, поэтому мы делаем ее дружественной функцией. Этой цели в объявлениях внутри каждого класса служит ключевое слово friend:
friend int frifunc(alpha, beta);
Это объявление может быть расположено где угодно внутри класса. Нет никакой разницы, запишем мы его в public- или в private-секцию Объект каждого класса передается как параметр функции frifunc(), и функция имеет доступ к скрытым данным обоих классов посредством этих аргументов. Функция выполняет не очень сложную работу по складыванию значений данных и выдаче их суммы. В main() осуществляется вызов этой функции и выводится на экран результат.Напоминаем, что к классу нельзя обращаться до того, как он объявлен в программе. Объявление функции frifunc() (класс alpha) ссылается на класс beta, а зна- Class beta; В начале программы. Надо отметить, что идея дружественных функций несколько сомнительна. Во время разработки C++ на эту тему велись споры и приводились аргументы против включения в язык такой возможности. С одной стороны, дружественные функции повышают гибкость языка, но, с другой стороны, они не соответствуют принципу ограничения доступа к данным, который гласит, что только функции-члены могут иметь доступ к скрытым данным класса. Дружественная функция объявляется таковой в том классе, к данным которого она захочет впоследствии получить доступ. Таким образом, программист, не имеющий доступа к исходному коду класса, не может сделать функцию дружественной. В этом смысле целостность данных сохраняется. Но все равно такие функции принципиально нечистоплотны и потенциально Дружественные классы Методы могут быть превращены в дружественные функции одновременно с определением всего класса как дружественного. Программа FRICLASS показывает, как это выглядит на практике. // friclass.cpp // Дружественные классы #include <iostream> using namespace std; /////////////////////////////////////////////////////////// class alpha { private: int data1; public: alpha(): data1(99) { } //конструктор friend class beta; //beta – дружественный класс }; /////////////////////////////////////////////////////////// class beta { //все методы имеют доступ public: //к скрытым данным alpha void func1(alpha a) { cout << "\ndata1=" << a.data1;} void func2(alpha a) { cout << "\ndata1=" << a.data1;} }; /////////////////////////////////////////////////////////// int main() { alpha a; beta b;
b.func1(a); b.func2(a); cout << endl; return 0; } Для класса alpha весь класс beta провозглашен дружественным. Теперь все методы beta имеют доступ к скрытым данным класса alpha (в данной программе это лишь единственная переменная data1). Обратите внимание: мы объявляем именно класс дружественным, используя выражение friend class beta; А можно было сделать и так: сначала объявить обычный класс beta: class beta затем объявить класс alpha, а внутри определения alpha объявить дружествен- friend beta;
Поиск по сайту: |
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Студалл.Орг (0.012 сек.) |