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

1 ... 7 8 9 [ 10 ] 11 12 13 ... 144


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( плохая реализация );

Эквивалентность имен - основополагающий принцип системы типов в С++, а правила совместимости размещения в памяти гарантируют возможность явных преобразований, используемых в низкоуровневых операциях. В других языках



1 ... 7 8 9 [ 10 ] 11 12 13 ... 144

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