|
Программирование >> Полиморфизм без виртуальных функций в с++
Статический контроль типов на вызов экьивалентной С-функции в сгенерированном коде: void stack push(this,c) /* сгенерированный С-код */ struct stack* this; char с; if ((this->top)>(this->max)) error( стек пуст ); *(this->top)++ = с; void g(p) struct stack* p; /* сгенерированный С-код */ { stack push(p,с); В каждой функции-члене указатель this ссылается на объект, для которого вызвана функция-член. По воспоминаниям Стью Фельдмана, в самой первой реализации С with Classes программист не мог напрямую обращаться к this. Как только мне указали на этот недостаток, я быстро его устранил. Без this или какого-то эквивалентного механиз.ма функции-члены нельзя было бы использовать для манипуляций со связанным списком. Указатель this - это вариация С++ на тему ссылки THIS из Simula Иногда меня спрашивают, почему this - это указатель, а не ссылка и почему он называется this, а не self. Дело в том, что, когда this был введен в С with Classes, механизма ссылок в нем еще не было, а терминологию С++ я заимствовал из Simula, а не из Smalltalk. Если бы функция stack.push() была объявлена с ключевым словом inline, то сгенерированный код выглядел бы так: void g(p) /* сгенерированный С-код */ struct stack* р; if ((p->top)>(p->max)) error( стек пуст ); *(p->top)++ = с ; Именно такой код написал бы программист, работающий на С. 2.6. Статический контроль типов у меня не сохранилось ни воспоминаний о дискуссиях, ни заметок, касающихся внедрения статического контроля типов в С With Classes. Синтаксис и семантика, впоследствии одобренные в стандарте ANSI С, просто были включены в готовом виде в первую реализацию С with Classes. После этого в результате серии экспериментов появились более строгие правила, ныне вошедшие в С++. Для меня статический контроль типов после опыта работы с Algol68 и Simula был абсолютной необходимостью, вопрос заключался только в том, в каком виде его реализовать. Чтобы не возникло конфликта с С, я решил разрешить вызовы необъявленных функций и не проверять для них типы. Конечно, это была дыра в системе типов, впоследствии предпринимались попытки устранить основной источник ошибок и уже в С++ проблему решили путем запрета на вызовы необъявленных функций. Одно наблюдение обрекло на провал все попытки найти компромисс и тем самым обеспечить большую сов.местимость с С: программисты, изучавшие С with Classes, забывали, как искать ошибки времени исполнения, вызванные несоответствием типов. Привыкнув полагаться на проверку и преобразования типов, которые обеспечивал С with Classes, пользователи уже не могли быстро отыскать глупейшие ляпы, просачивавшиеся в С-программы, где контроля типов не было. К тому же забывалось о мерах предосторожности против таких ошибок, которые опытные программисты на С принимают, не задумываясь. Ведь в С with Classes такого просто не бывает . Таким образом, число ошибок, вызванных необнаруженным несоответствием типов аргументов, становится меньше, но их серьезность и время, потраченное на их 1юиск, возрастают. А в результате - недовольство программистов и требование ужесточить систему контроля типов. Самым интересным экспериментом с неполным статическим контролем типов была попытка разрешить вызовы необъявленных функций, но запоминать типы переданных аргументов, с тем чтобы при повторно.м вызове их можно было проверить. Когда Уолтер Брайт (Walter Bright) много лет спустя независимо открыл этот способ, он назвал его автопрототипированием, вспомнив употребляемый в ANSI С термин прототип, обозначающий объявление функции. Опыт показал, что автопрототипирование позволяло отловить многие ошибки, и поначалу доверие программистов к системе контроля типов повысилось. Однако ошибки в функции, которая вызывалась только один раз, таким способом не обнаруживались, и из-за этого автопрототипирование окончательно подорвало веру в проверку типов и привело к еще худшей навязчивой идее, нежели все, с чем я встречался в С или BCPL. В С with Classes введена нотация f (void) для функции f, не принимающей аргументов, вместо нотации f () в С, обозначающей функцию с любым числом аргументов без проверки типов. Однако вскоре пользователи убедили меня, что нотация f (void) неизящна, а объявление с помощью f (} функции, которая может принимать аргументы, далеко от интуитивно очевид1юго. Поэтому после некоторых экспериментов было решено оставить нотацию f () для функции, не принимающей никаких аргументов, ибо именно этого ожидает неискушенный пользователь. Чтобы отважиться на такой разрыв с традициями С, мне потребовалась поддержка со стороны Дуга Макилроя и Денниса Ричи. Только после того как они охарактеризовали нотацию f (void) словом гадость , я решил придать f () очевидную семантику. Однако и по сей день правила контроля типов в С гораздо слабее, че.м в С++, а комитет по стандартизации ANSI С одобрил отвратительную f (void) , впервые появившуюся в С with Classes. 2.6.1. Сужающие преобразования Еще одной ранней попыткой усилить контроль типов в С with Classes был запрет на неявные преобразования, приводящие к потере информации. Как и многие другие, я не раз попадался в ловушку, иллюстрируемую следующими примерами (конечно, в реальных программах найти такую ошибку будет потруднее): void f() { long int Ing = 65000; Статический контроль типов int il = Ing; /* il становится отрицательным (-53 6) */ /* на машинах с 16-разрядными целыми */ int 12 = 257; char с = 12; /* отбрасывание: с становится равным 1 */ /* на машинах с 8-разрядным char */ Я решил попробовать запретить все преобразования, не сохраняющие значение, то есть потребовать явного оператора преобразования в случаях, когда больший объект копируется в меньший: void gdong Ing, int i) /* эксперимент */ { int il = Ing; /* ошибка: преобразование понижает тип */ il = (int)Ing; /* отбрасывание для 16-разрядных целых */ char с = i; /* ошибка: преобразование понижает тип */ с = (char)i; /* отбрасывание */ Эксперимент с треском провалился. Все С-программы, которые я просмотрел, содержали множество присваиваний значений типа int переменным типа char. Разумеется, коль скоро программы работали, большинство таких присваиваний было безопасно: либо значение было маленьким и не обрезалось, либо отбрасывание старших разрядов предполагалось или, по крайней мере, в данном контексте считалось безвредным. Пользователи С with Classes вовсе не желали такого отхода от С. Я все еще пытаюсь найти какой-то способ разрешить эти проблемы (см. раздел 14.3.5.2). 2.6.2. О пользе предупреждений я рассматривал возможность проверки присваиваемых значений во время исполнения, но за это пришлось бы заплатить серьезным уменьшением скорости и увеличением размера кода. К тому же, на мой взгляд, диагностировать ошибку было уже поздновато. Проверки преобразований во время исполнения - да и любые другие проверки на данной стадии - были отнесены к разряду идей о будущей поддержке в отладочной среде . Вместо этого я применил способ, позже ставший стандартным для обхождения с теми }!едостатками С, которые, па мой взгляд, были слишком серьезными, чтобы игнорировать их, и слишком укоренившимися в С, чтобы их устранять. Я заставил препроцессор С with Classes (а позже и свой компилятор С++) выдавать предупреждения: void fdong Ing, int i) { int il = Ing; неявное преобразование: предупреждение il = (int)Ing; явное преобразование: нет предупреждения char с = i; слишком устойчивая идиома: нет предупреждения
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |