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

1 ... 71 72 73 [ 74 ] 75 76 77 ... 144


void

int,double);

void

f(double,int) ;

void

complex,int)

void

int ...);

void

complex ...)

void

complex z)

f(1,2.0) f(1.0,2) f(z,1.2)

f(int,double) f(double,int) f(complex,int)

между переданными при вызове типами формальных и фактических аргументов. Предпочтительными считались такие соответствия, при которых выполнялись наименее опасные преобразования. Это позволило избежать противоречий со стандартными правилами расширения типов и старгдартными преобразованиями в С. В [Stroustrup, 1989b] для версии 2.0 данная схема описана так:

...за исключением немногих случаев, когда старые правило могли зависеть от порядка, новые совместимы, и ранее написанные программы дадут те же результаты, что и до введения новых правил. Компиляторы С++, выпущенные за последние два года, выдавали предупреждения по поводу тех зависящих ат порядка разрешений неоднозначности, которые теперь объявлены вне закона .

В С++ различаются пять видав соответствий:

□ не требующее никаких или требующее только неизбежных преобразований (например, имени массива в указатель, имени функции в указатель но функцию и Т в const т);

□ нуждающееся в расширении интегральных типов (в соответствии са стандартом ANSI С, то есть: char в int, short в int и их unsigned аналогов), а также float в double;

□ требующее стандартных преобразований (int в double, unsigned int в int, derived* в base* и т.п.);

□ работающее с определенными пользователем преобразованиями (конструкторами и конверторами);

□ учитывающее мнагатачия (...) в объявлении функции.

Рассмотрим сначала функции с одним аргументом. Всегда нужно выбирать лучшее соответствие, а именно то, которое расположено выше в приведенном списке. Если есть два лучших соответствия, то вызов признается неоднозначным и компилятор выдает ошибку .

Приведенные выше примеры иллюстрируют правило. Более точное его изложение можно найти в ARM.

Следующее правило необходимо соблюдать, когда функция имеет более одного аргумента [ARM]:

Если при вызове передается более одного аргумента, то выбирается функция, у которой хотя бы для одного из них соответствие лучше, а для остальных не хуже. Например:

class complex { . . .

complex(double);



f(z,l,3); f(complex ...)

f(2.0,z); f(int ...)

f(l,l); ошибка: неоднозначность

f(int,double) или f(double,int)

Нежелательное сужение типа double до int в третьем и предпоследнем вызовах влечет 30 собой предупреждение компилятора. Такое сужение розрешено в целях сохранения совместимости с С. В данном случае сужение безвредно, но во многих других может привести к искажению значения, поэтому необоснованно применять его не стоит .

Уточнение формулировки этого правила для нескольких аргументов ггривело к появлению правила пересечения [ARM, стр. 312-314]. Впервые его сформулировал Эндрю Кениг в беседе с Дугом Макилроем, Джонатаном Шопиро и мной. Кажется, именно Шопиро обргаружил странные примеры, доказавпше необходимость этого правила [ARM, стр. 313].

Обратите внимание на то, как серьезно мы подходили к вопросам совместимости. Если бы, принимая проектные решения, мы систематически отдавали предпочтение простоте и изяществу, а ire совместимости, то сегодня С++ был бы куда ко.мпактнее и чище. Но в то же вре.мя он был бы языком, интересны.м лишь малой горстке ценителей.

11.2.3. Нулевой указатель

Кажется, ничто не вызвало более жарких споров, чем вопрос о том, как правильно обозначить нулевой указатель (пе указывающий ни на какой объект). С++ унаследовал определение пулевого указателя от классического С [Kernighan, 1978]:

Константное выражение со значением О преобразуется в указатель, обычно называемый нулевым. Гарантируется, что такой указатель можно отличить от указателя но любой объект или функцию .

ARM дополнительно предупреждает:

Отметим, что нулевой указатель не обязательно представляется той же комбинацией битов, что и целый О .

Это предостережение против распространенного заблуждения, что если выражение р=0 делает указатель р нулевым, то представление нулевого указателя должно быть в точности таки.м, как у целого нуля, то есть состоять из одних нулевых битов. Но С++ достаточно сильно типизирован, для того чтобы позволить разработчику компилятора выбрать для нулевого указателя любое представление, независимо от того, как он выглядит в исходно.м тексте программы. Одно из исключений - использование многоточия для подавления контроля аргументов функции:

int printf(const char* . . .) ; неконтролируемый вызов в стиле С

printf(fmt. О, (char)O, (void*)0, (int*)0, (int(*) ())0);

Здесь необходи.мы приведения типов для явного указания, какой О требуется. В это.м примере могло бы быть передано пять разных значений.



В K&R С аргументы функции вообще не проверялись, и даже в ANSI С нельзя полагаться на контроль аргументов, поскольку он необязателен. Из-за этого обнаружить все нули в програм.ме на С или С-1-+ нелегко. А поскольку в других языках принято использование символической константы для представления пулевого указателя, то программисты на С традиционно применяли для этой цели макрос NULL. К сожалению, в K&R С нет корректного переносимого определения NULL, в ANSI С разумны.м и все чаще употребляюпп1мся определением NULL является (void*) 0.

Однако в С++ и (void*) О нельзя счесть удачным обозргачение.м нулевого указателя:

char* р = (void*)0; корректно в С, но ке в С++

Указатель void* нельзя присвоить ничему без явного приведения типа. Если разрешить неявное преобразование void* к друго.му типу указателя, то появится серьезная прореха в системе типов. Можно было бы считать (void* ) О специальным случаем, но вводить его стоит только тогда, когда ничего другого не остается. Кроме того, порядок применения С++ был определен задолго до принятия стандарта ANSI С, и я ие хочу, чтобы особо важная часть С++ зависела от макросов (см. главу 18). Поэто.му я использовал просто 0. Те, кто не может жить без символической константы, обычно определяют нечто похожее на

const int NULL = 0; или #define NULL О

Для компилятора определенный таким образом NULL и О - синонимы. К сожалению, многие включили в свой код определения NULL, NIL, Null, null и т.д., так что вводить еще одно просто опасно.

Есть одна ошибка, которую невозможно обнаружить, если О (как бы он ин был назван) используется в качестве нулевого указателя. Рассмотрим пример:

void f (char*);

void gO { f(0); } вызывает f(char*)

Если добавить еще одну функцию f (), то семантика g () изменится без предупреждения:

void f(char*); void f (int);

void g() { f(0); } вызывает f(int)

Этот неприятный побочный эффект возникает из-за того, что О - это значе-1гие типа int, которое может быть расширено до нулевого указателя, а не пря.мое обозначение последнего. Думаю, что хороший компилятор .мог бы выдать предупреждение, но во вре.мя работы над Cfront я об этом не поду.мал. Можно было бы считать вызов f (О) неоднозначным, а не разрешать его в пользу f (int), но это могло бы не понравиться тем, кто хочет видеть особый смысл в NULL или nil.



1 ... 71 72 73 [ 74 ] 75 76 77 ... 144

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