Программирование >>  Полиморфизм без виртуальных функций в с++ 

1 ... 9 10 11 [ 12 ] 13 14 15 ... 144


Статический контроль типов

на вызов экьивалентной С-функции в сгенерированном коде:

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; слишком устойчивая идиома: нет предупреждения



1 ... 9 10 11 [ 12 ] 13 14 15 ... 144

© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки.
Яндекс.Метрика