|
Программирование >> Полиморфизм без виртуальных функций в с++
Клосс, где есть хотя бы одно исключительно виртуальная функция, нозывоется абстрактным. Его можно использовоть только в качестве бозового для какого-то другого классо. В частности, нельзя создовать объекты абстрактного класса. Класс, производный от абстрактного, должен либо содержать определения исключительно виртуольных функций, либо снова объявить их такими. Концепции исключительно виртуольных функций было отдано предпочтение по сравнению с явным объявлением класса абстрактным, поскольку выборочное определение функций окозы-воется номного более гибким . Как показано выше, в С-ы- и раньше была возможность выразить понятие абстрактного класса, но для этого требовалось проделать больше работы, чем хотелось бы. Некоторые пользователи считали это важным вопросом (см. например, [Johnson, 1989]). Но лишь за несколько недель до выпуска версии 2.0 я осознал, что очень немногие вообще понимают эту концепцию и что непонимание сути абстрактных классов было источником многих ошибок проектирования. 13.2.2. Абстрактные типы Типичной претензией к С++ было (и остается) то, что закрытые данные включаются в объявление класса. Поэтому при изменении закрытых данных приходится перекомпилировать весь код, в котором данный класс используется. Часто недовольство по этому поводу формулировали так: абстрактные типы в языке С++ не являются по-настоящему абстрактными или данные на самом деле не являются скрытыми . Многие пользователи думали, что уж если они могут поместить представление объекта в закрытую секцию класса, то обязательно должны так поступить. Разумеется, это неправильно (поэтому-то данная проблема многие годы и ускользала от моего внимания). Если вы не хотите помещать представление в класс, так и пе надо! Просто перенесите спецификацию представления в какой-то производный класс. Нотация абстрактных классов позволяет сделать это явно. Например, можно так определить множество указателей типа Т: class set { public: virtual void insert(T*) =0; virtual void remove(T*) =0; virtual int is meraber(T*) =0; virtual T* first 0 =0; virtual T* nextO =0; virtual -setO { } Здесь есть вся информация, необходимая для использования класса set, а также отсутствуют детали представления или реализации. Только тот, кто действительно создает объекты классов set, должен знать, как представлено множество. Например, если есть объявление class slist set : public set, private slist ( slink* current elem; public: void insert(Т*); void remove(T*); int is member(T*); T* first 0 ; T* next(); slist set() : slistO, current elem(0) { } to можно создать объекты slist set, с которыми смогут работать пользователи класса set, даже не подозревающие ни о каком slist set. Например: void userl(set& s) { for (Т* p = s.firstO; p; p=s.next()) { использовать p void user2() { slist set ss; . . . userl(ss); Важно, что функцию, в которой используется абстрактный класс set, например userl (), можно откомпилировать, не включая заголовочные файлы, содержащие определение slist set и классы типа slist, от которых последнее зависит. Как уже отмечено выше, компилятор не позволяет создать объект абстрактного класса. Например: void f(set& si) правильно { set s2; ошибка: объявление объекта абстрактного класса set set* р = 0; правильно set& s3 = si; правильно Концепция абстрактных классов важна пото.му, что позволяет провести более четкую границу между пользователем и разработчиком реализации. Абстрактный класс - интерфейс к реализациям, содержащимся в производных от него классах. Это уменьшает число перекомпиляций после изменений, равно как и объем информации, необходимой для компиляции обычного фрагмента кода. С помощью абстрактных классов уменьшается зависимость пользователя от разработчика, снимаются претензии тех, кто жалуется на долгую компиляцию. Данное средство заодно служит и интересам поставщиков библиотек, которым больше не нужно беспокоиться, как изменения реализации отразятся на работе пользователей. Я видел системы, время компиляции которых уменьшилось на порядок после того, как основные интерфейсы были реализованы в виде абстрактных классов. Попытка объяснить все это в [Stroustrup, 1986b] оказалась безуспешной, и лишь после появления в языке явной поддержки для абстрактных классов моя .мысль стала понятной пользователям [2nd]. 13.2.3. Синтаксис Странный синтаксис =0 был выбран вместо очевидной альтернативы ввести ключевое слово pure или abstract только потому, что тогда я не видел возможности добавить новое ключевое слово. Если бы я предложил pure, то версия 2.0 вышла бы без абстрактных классов. Чтобы не рисковать сроками выпуска версии и не навлекать на себя упреков по поводу нового ключевого слова, я воспользовался традиционным для С и C-i-i- соглашением о том, что О означает отсутствует . Синтаксис = О не противоречит моим взглядам на тело функции как на инициализатор функции, а также представлениям (упрощенны.м, но обычно соответствующим реальности) о том, что множество виртуальных функций реализовано в виде вектора указателей на функции (см. раздел 3.5.1). На само.м деле, реализация синтаксиса = 0 путем помещения О в vtbl - не лучший способ. Я помещал в vtbl указатель на функцию pure virtual called; затем эту функцию мож1ю было определить так, чтобы она выводила подходящее сообщение об ошибке во время выполнения. Лучше объявлять отдельные функции исключительно виртуальными, а не весь класс - абстрактным, поскольку в этом случае удается добиться большей гибкости. Я высоко ценю возможность определять класс поэтапно, то есть часть виртуальных функций определить в базовом классе, а остальные - в производных. 13.2.4. Виртуальные функции и конструкторы Конструирование объекта из базовых классов и членов (см. раздел 2.11.1) влияет на работу механизма виртуальных функций. Подчас по этому поводу возникала путаница. Поэтому здесь объясняется, почему избранный для С++ путь является почти безальтернативным. 13.2.4.1. Вызов исклизчитвльно виртуальной функции Как вообще можно вызвать чисто виртуальную функцию, а не замещающую ее в одном из производных классов? Объекты абстрактных классов могут существовать только в качестве базовых для каких-то других классов. После того как объект производного класса сконструирован, наша виртуальная функция уже замещена какой-то другой, определенной в производном классе. Однако в процессе конструирования собственный конструктор абстрактного класса может по опшбке вызвать исключительно виртуальную функцию: class А { public: virtual void f() =0;
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |