|
Программирование >> Полиморфизм без виртуальных функций в с++
В с принято, что символьный литерал (а) имеет тип int. Как пи странно, приписывание а типа char в С++ не приводит к проблемам сов.местимости. Если не считать примера sizeof ( а ), то любая конструкция, которая может быть записана в С и С++, приводит к одному и тому же результату. При }газ1гачепии символьному литералу типа char я отчасти опирался па сообщение Майка Тимана об опыте использования в GNU С++ флага компилятора, обеспечиваюп1его такую интерпретацию. С другой стороны, выяснилось, что разницу .между const и ne-const можно успешно использовать. Наглядным при.мером такой перегрузки является пара функций char* strtok(char*, const char*); const char* strtok(const char*, const char*); в качестве альтернативы стандартной функции из библиотеки ANSI С char* strtok(const char*, const char*); Функция strtok () из библиотеки С возвращает подстроку константной строки, переданной в качестве первого аргумента. Описать данную подстроку без квалификатора const в С++ невозможно, поскольку это расценивается как неявное 1гарушение системы типов. С другой стороны, песов.местимость с С должна быть сведена к миии.муму, поэто.му предоставлергие двух вариантов функции strtok представляется наиболее разу.мны.\г вариантом. Допущение перегрузки па осргове на/гичия илц отсутствия const - часть общей стратегии ужесточения правил употребления const (см. раздел 13.3). Опыт показал, что при сопоставлении функций следует принимать во внимание иерархии, образованные в результате открытого наследования. При наличии выбора следует отдавать предночтергие преобразованию в самый низший в иерархии класс. Аргу.мент void* выбирается лишь в то.м случае, если никакой другой указатель не подходит. Например: class В {/*...*/ }; class ВВ : public В { /* ... */ }; class ВВВ : public ВВ { /* ... */ }; void f(В*); void f(ВЗ*); void f(void*); void g(BBB* pbbb, BB* pbb, B* pb, int* pi) { f(pbbb); f(BB*) f(pbb); f(BB*) f(pb); f(B*) f(pi); lit (void*) abs(l.OF); abs(float) abs(a); abs(char) Данное правило разрешения неоднозначности соответствует правилу для вызова виртуальных функций, в соответствии с которым выбирается член самого нижнего в иерархии класса. Изменение было очевидным. В результате исчезли опшбки. Правда, у этого правила есть одно интересное свойство. Оно устанавливает void* в качестве корня дерева преобразовании классов. Это согласуется с представлением о том, что конструктор создает объект в неформатированной памяти, а деструктор преврашает объект в нее (см. ра,зделы 2.11.1 и 10.2). Преобразова1ше вида В* в void* позволяет рассматривать объект как неформатированную память, если никакие другие его свойства не представляют шгтереса. 11.2.2. Управление неоднозначностью в первоначальном механиз.ме перегрузки разрепшние неоднозначностей зависело от порядка объявлергий. Объявления рассматривались поочередно, и предпочтение отдавалось тому, которое встретилось в тексте раньше. Чтобы и,збежать недоразумений, при сопоставлении принимались во внимание только преобразования, не сужающие тип. Напри.мер: overload void print(int); первоначальные (до 2.0) правила: void print(double); void g() { print(2.0); print(double): print(2.0) преобразование double->int не рассматривается print(2.OF); print(double): print(double(2.OF)) преобразование float->int не рассматривается преобразование float->double рассматривается print(2); print(int): print(2) Это правило легко формулируется, эффективно ко.мпилируется, доступно для понимания, тривиально реализуется в ко.мпиляторе, но является иостояш1ы.м источником ошибок. Изменение порядка объявлений на обратный полностью mciw-ет смысл кода: overload void print(double); первоначальные правила: void print(int); void g() { print(2.0); print(double): print(2.0) print(2.OF); print(double): print(double(2.OF)) преобразование float->double рассматривается Таким образом, зависимость от порядка чревата ошибками. Это стало серьезным препятствием на пути эволюции С-и- в направлении более интенсивного использования библиотек. Моей цели - распространить взгляд на программирование как на составление программ из независимо разработанных кусков (см. также раздел 11.3) - наряду со многими другими факторами препятствовала и зависимость от порядка. Независимые от порядка правила перегрузки весьма усложняют определение и реализацию С-и-, поскольку следует поддерживать совместимость как с С, так и с предыдушим вариантом С-и-. В частности, не годилось простое правило: Выражение, которое может быть корректно интерпретировано хотя бы двумя разными способами, неоднозначно и, стало быть, незаконно . Если принять данный тезис, то незаконными будут все вызовы print (), приведенные в примере выше. Я решил, что требуется некое правило лучшего соответствия , которое позволило бы предпочесть точное соответствие типов соответствию, требующему преобразований, а безопасные преобразования (типа float в double) - небезопасным (сужающим тип, искажающим значение и т.д., например, float в int). В результате обсуждения, уточнения и ревизии продолжались несколько лет. Некоторые детали все еще обсуждаются комитетом по стандартизации. Вместе со мной наиболее активное участие в процессе принимали Дуг Макилрой, Энди Кениг, Джонатан Шопиро. Еще на ранней стадии Дуг заметил, что мы чересчур близко подошли к попытке спроектировать естественную систему для неявных преобразований. Он считал правила PL/I (которые помогал проектировать сам) доказательством того, что такую естественную систему нельзя создать для богатого набора типов данных - а в C-t-t- есть весьма богатый набор встроенных типов с нерегулируемыми правилами преобразований, а также возможность определять преобразования между произвольными типами, определенными пользователем. Желание сохранить совместимость с С, ожидания пользователей, намерение разрешить им определять типы, с которыми можно было бы работать точно так же, как с встроенными, - все это не позволяло запретить неявные преобразования. Полагаю, что решение оставить неявные преобразования было правильным. Я также согласен с наблюдением Дуга, что задача свести к минимуму количество неприятностей, которые могут преподнести неявные преобразования, сложна по существу. Согласен я и с тем, что полностью их избежать не удастся (по крайней мере, при соблюдении требования о совместимости с С). Просто у программистов разные ожидания, поэтому, какое правило ни выбери, для кого-то оно обернется неприятным сюрпризом. Фундаментальная проблема заключается в том, что граф неявных преобразований встроенных типов содержит циклы. Так, существует неявное преобразование не только из char в int, но и из int в char. Это потенциальный источник бесконечного числа мелких ошибок. Поэтому было решено отказаться от схемы, основанной на графе преобразований. Вместо нее мы изобрели систему соответствий print(2); print(double): print(double(2)) преобразование int->double рассматривается
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |