|
Программирование >> Полиморфизм без виртуальных функций в с++
return *--top; То, что при этом объявление класса несколько теряет наглядность, не осталось без внимания, но было сочтено правильным, поскольку препятствовало чрезмерному применению встраиваемых функций. Ключевое слово inline и возможность встраивать функции, не являющиеся членами, появились позже, уже в С++. Так, в С++ этот пример можно переписать иначе: class stack { С++ ... char pop(); inline char stack::pop() С++ { if (top <= min) error( стек пуст ); return *--top; Директива inline - лишь совет, который компилятор может игнорировать и часто так и поступает. Это вызвано логической необходи.мостью, поскольку можно написать рекурсивную встраиваемую функцию, а на этапе компиляции невозможно доказать, что рекурсия не окажется бесконечной. Попытка встраивания такой функции привела бы к зациклива1шю компилятора. Прида1ше слову inline статуса совета и.меет и практическую пользу, поскольку позволяет автору компилятора обработать те случаи, когда встраивание невоз.можно, и просто отказаться от него. Для С with Classes, как и для всех его преем1шков, было необходимо, чтобы встраиваемая функция имела в програ.мме единстве1пюе определение. Определение такой функции, как pop (), приведенной выше, в разных единицах компиляции привело бы к хаосу, игнорированию системы контроля типов. Но в условиях раздельной компиляции трудно гарантировать, что в большой системе данное правило не нарушено. В С with Classes это не проверялось, и в большинстве реализаций С++ до сих пор нет гарантий, что встраиваемая функция не определена по-разному в разных единицах компиляции. Однако теоретическая пробле.ма не переросла в практическую в основном потому, что встраиваемые функции обычно определяют в заголовочных файлах в.месте с классами, а объявления классов в программе также должны быть уникальны. 2.5. Модель компоновки Вопрос о то-м, как скомпоновать раздельно отко.мпилированные фрагменты программы, важен для любого языка програм.мирования и до некоторой степени определяет воз.можности языка. На разработку С with Classes и С++ во многом повлияли следующие решения: □ раздельная компиляция может осуществляться с использованием стандартных компоновщиков (редакторов связей) для C/Fortran, применяемых на платформах UNIX и DOS; □ компоновка должна быть типобезопасной (не противоречить системе контроля типов); □ компоновка не должна нуждаться в какой бы то ни было базе данных (хотя ее использование в конкретных реализациях для повышения эффективности не запрещается); □ компоновка с фрагментами программы, написанными на других языках,-например на С, Fortran или ассемблере, должна быть простой и эффективной. Чтобы обеспечить непротиворечивость раздельной компиляции в С, используются заголовочные файлы, которые обычно дословно включаются в каждый исходный файл, где соответствующее объявление необходимо. В них помещаются объявления структур данных, функций, переменных и констант. Непротиворечивость обеспечивается за счет того, что в заголовочные файлы помещается вся необходимая информация, доступ к которой производится только путем включения этих файлов. С++ следует этой модели, но только до определенного момента. В объявлении класса в С++ может (хотя и необязательно, см. раздел 13.2) быть описано размещение объекта. Это разрешено для того, чтобы упростить и сделать эффективным объявление истинно локальных переменных. Рассмотрим такую функцию: void f() { class stack s; int c; s.push(h); с = s.popO ; Реагируя на объявление класса stack (см. разделы 2.3 и 2.4.1), даже простейшая версия С with Classes сможет сгенерировать для этого примера код, где: динамическая память не используется, функция pop () встроена так, что ее вызов не связан с накладными расходами, а при обращении к push () вызывается невстра-иваемая, отдельно скомпилированная функция. В этом отношении С++ напоминает язык Ada. В то время я полагал, что можно найти какой-то компромисс между двумя подходами: □ отделение объявления интерфейса от реализации (как в Modula-2) в сочетании с подходящим инструментом (редактором связей); □ наличие единого объявления класса в сочетании с инструментом (анализатором зависимостей), который будет рассматривать интерфейс отдельно от деталей реализации с целью определить, в каких случаях нужна повторная компиляция. Похоже, я недооценил сложность последнего решения, а сторонники первого подхода - его стоимость (с точки зрения переносимости и затрат во время исполнения). Еще больше я осложнил жизнь пользователям С++, не объяснив должным образом, как можно воспользоваться производными классами для отделения интерфейса от реализации. Разумеется, я пытался растолковать это (см. пример в [Stroustrup, 1986, §7.6.2]), но почему-то не был понят. Думаю, причина неудачи в том, что мне никогда не приходила в голову простая мысль: многие (если не большинство) программистов, работая с С++, думают, что раз можно поместить представление прямо в объявление класса, описывающего интерфейс, то это обязательно нужно сделать. Я не пытался создать инструменты типобезопасной компоновки для С with Classes, они появились лишь в версии С++ 2.0. Однако я помню разговор с Деннисом Ричи и Стивом Джонсоном о том, что безопасность с точки зрения типов при пересечении границ единиц компиляции должна была стать частью С. Просто не было возможностей гарантировать это для реальных программ, так что пришлось полагаться на инструменты типа Lint [Kernighan, 1984]. В частности, Стив Джонсон и Деннис Ричи утверждали, что в С предполагалось обеспечить эквивалентность имен, а не структур. Например, объявления struct А { int X, у; }; Struct в { int X, у; }; определяют два несовместимых типа А и В. Далее, объявления struct с { int X, у; }; в файле 1 struct С { int X, У; }; в файле 2 определяют два разных типа с одним и тем же названием С, и компилятор, способный осуществлять сквозную проверку в разных единицах компиляции, должен был бы выдать ошибку повторное определение . Это правило призвано снять некоторые проблемы при сопровождении программы. Подобные повторяющиеся объявления возникают, по всей вероятности, при копировании текста из одного файла в другой. Но после этой операции объявление вполне может измениться. И если, изменив его в одном файле, не сделать то же самое в другом, программа просто перестанет работать. На практике С, а за ним и С++ гарантируют, что структуры типа А и В, приведенные выше, одинаково размещаются в памяти, так что их можно приводить друг к другу и использовать очевидным образом: extern f(struct А*); void g(struct A* pa, struct B* pb) f(pa); /* правильно */ f(pb); /* ошибка: ожидается A* */ pa = pb; /* ошибка: ожидается A* */ pa = (struct A*)pb; /* правильно: явное преобразование */ pb->x = 1; if (pa->x != pb->x) error( плохая реализация ); Эквивалентность имен - основополагающий принцип системы типов в С++, а правила совместимости размещения в памяти гарантируют возможность явных преобразований, используемых в низкоуровневых операциях. В других языках
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |