|
Программирование >> Полиморфизм без виртуальных функций в с++
2.8.3. Важность синтаксиса Я полагаю, что большинство людей уделяет слишком много внимания синтаксису в ущерб вопросам контроля типов. Однако для дизайна C+-I- критичными всегда были проблемы типизации, недвусмысленности и контроля доступа, а вовсе не синтаксиса. Не хочу сказать, что синтаксис не имеет никакого значения. Напротив, он исключительно важен. Удачный синтаксис помогает программисту при изучении новых концепций и позволяет избегать глупых ошибок, поскольку записать их труднее, чем их правильные альтернативы. Однако синтаксис }1адо проектировать так, чтобы он соответствовал семантическим понятиям языка, а не наоборот. Отсюда следует, что центральной темой при обсуждении языка должен быть вопрос о том, что можно выразить, а не как это сделать. Тонким нюансом в дискуссиях о совместимости с С было то, что опытные С-програ,ммисты давно свыклись с тем, как та или иная операция осуществлялась раньше, и потому были совершенно нетерпимы к несовместимостям, которые требовались для поддержания такого стиля программирования, который в дизайне С не был заложен. И наоборот, программисты, не писавшие на С, обычно недооценивают значимость синтаксиса для С-программистов. 2.9. Производные классы Концепция производного класса - это адаптация префиксной нотации классов, принятой в Simula, и уже поэтому она схожа с концепцией подкласса в Smalltalk. Названия производный и базовый были выбраны потому, что я никак не мог запомнить, что есть sub, а что - super, и заметил, что такие сложности возникают не только у меня. Вот еще одно наблюдение: для многих тот факт, что подкласс обычно содержит больше информации, чем его суперкласс, кажется неестественным. Изобретая термины производный и базовый , я отошел от своего принципа не вводить новых названий, если существуют старые. В свою защиту скажу лишь, что я никогда не замечал путаницы по поводу этих терминов и что запомнить их очень просто даже тем, что не имеет математического образования. В С with Classes для концепции производного класса не было никакой поддержки во время исполнения. В частности, отсутствовало понятие виртуальной функции, которое было в Simula, а позже вошло в C-I-4-. Я просто сомневался (думаю, не без оснований) в своей способности научить людей ими пользоваться и, более того, в способности убедить, что для типичных применений виртуальная функция так же эффективна по скорости и по памяти, как обычная. Многие пользователи, имеющие опыт работы с Simula или Smalltalk, до сих пор в это не верят, пока им не объяснишь подробно, как это сделано в C-i-i-, а некоторые и после испытывают сомнения. Даже и без виртуальных функций производные классы в С with Classes были полезны для построения новых структур данных из старых и для ассоциирования операций с получающимися типами. Так, с их помощью удалось определить классы связанного списка и задачи (task). Производные классы 2.9.1. Полиморфизм без виртуальных функций Даже в отсутствие виртуальных фу}!кций пользователь мог работать с объектами производного класса, а устройство базового класса трактовать как деталь реализации. Напри.мер, если имеется класс вектора с элементами, проиндексированными начиная с О и без проверки выхода за границы диапазона class vector { /* ... */ int get elem(int i); to }!a его основе можно построить вектор элементов с индексами из заданного диапазона с контролем выхода за его границы: class vec : vector { int hi, lo; public: /* ... */ new(int lo, int hi); get elem(int i); int vec.get elem(int i) { if (i < lo I I hi < i) error( выход за границы диапазона ); return vector.get elem(i - lo) ; Можно было вместо этого ввести в базовый класс явное поле типа и использовать его совместно с явным приведением типов. Первая стратегия при.менялась, когда пользователь видел только конкретные производные классы, а система видела все их базовые классы. Вторая стратегия полезна тогда, когда базовый класс реализовывал по сути дела одну вариантную запись для множества производных классов. Например, в работе [Stroustrup, 1982b] приведен следующий уродливый код для извлечения объекта из таблицы и использования имеющегося в нем поля типа: class elem { /* свойства, хранящиеся в таблице */ }; class table { /* табличные данные и функции поиска */ }; class cl name * cl; /* cl name - производный от elem */ class po name * po; /* po name - производный от elem */ class hashed * table; /* hashed - производный от table */ elem * p = table->look( carrot ) ; if (P) { switch (p->type) { /* поле типа type в объектах класса elem */ case PO NAME: po = (class po name *) p; /* явное преобразование типа */ break; case CL NAME: cl = (class cl name *) p; /* явное преобразование типа */ break; default: error( неизвестный тип элемента ); else error( carrot не определено ); При проектировании С with Classes и С++ было потрачено много усилий ради того, чтобы программисту не приходилось писать такой код. 2.9.2. Контейнерные классы без шаблонов в то время в моих программах и размышлениях важное место занимал вопрос о том, как с помощью комбинирования базовых классов, явных преобразований типов и (изредка) макросов можно получить обобщенные контейнерные классы. Например, в работе [Stroustrup, 1982b] продемонстрировано, как из списка объектов типа link построить список, который может содержать объекты одного типа: class wordlink : link { char word[SIZE]; public: void clear(void); class wordlink * get(void); { return (class wordlink *) link.getO; }; void put(class wordlink * p) { link.put(p); }; Поскольку каждый объект link, который с помощью функции put () помещается в список, должен принадлежать классу wordlink, то обратное приведение к wordlink объекта типа link, извлекаемого из списка функцией get (), безопасно. Отмечу использование закрытого (по умолчанию, в отсутствие ключевого слова public в спецификации базового класса link; см. раздел 2.10) наследования. Если разрешить использовать wordlink как просто link, то была бы поставлена под уфозу безопасность работы с типами. Для обеспечения обобщенных типов использовались макросы. Вот как об этом написано в [Stroustrup, 1982b]: В примере класса stack, упомянутом во введении, явно определялся стек символов. Иног-до это оказывается слишком частным случаем. А что, если нужен еще и стек длинных целых? А если класс stack предполагается использовать в библиотеке, когда тип элемента, помещаемого в стек, заранее неизвестен? В таких случаях объявление класса stack и ассоциированных с ним функций следует написоть ток, чтобы при создании стека тип элементов можно было передать порометром так же, как размер стека.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |