|
Программирование >> Полиморфизм без виртуальных функций в с++
class stack * p2 = new stack; /* p2 указывает на объект класса stack, размещенный в куче */ si.push(h); /* прямое обращение к объекту */ pl->push(S); /* обращение к объекту по указателю */ Из этого примера видны несколько ключевых проектных решений: □ по аналогии с Simula язык С with Classes позволяет задать типы, из которых создаются переменные (объекты), тогда как, скажем, Modula описывает модуль как набор объектов и функций. В С with Classes класс - это тип (см. раздел 2.9), что стало главной особенностью С++. Но если слово class определяет в С++ пользовательский тип, то почему я не назвал его type? В 0СН0В1ЮМ потому, что я не люблю изобретать новую терминологию, а соглашения, принятые в Simula, меня в большинстве случаев устраивали; □ представление объектов пользовательского типа - часть объявления класса, что имеет далеко идущие последствия (см. разделы 2.4 и 2.5). Например, это означает следующее: настоящие локальные переменные пользовательского типа можно реализовать без использования кучи (ее еще называют динамической памятью) и сборки мусора. Отсюда также следует, что при изменении представления объекта все функции, использующие его напрямую (не через указатель), должны быть перекомпилированы. (См. раздел 13.2, где описаны средства С++ для определения интерфейсов, позволяющие избежать такой перекомпиляции.); □ контроль доступа используется еще на стадии компиляции для ограничения доступа к элементам представления класса. По умолчанию имена членов класса могут встречаться только в функциях, упомянутых в объявлении класса (см. раздел 2.10). Члены (обычно функции-члены), описанные в открытом интерфейсе класса - части, следующей за меткой public:, -могут использоваться и в других местах программы; □ для функций-членов указывается полный тип (включая тип возвращаемого значения и типы формальных аргументов). На основе данной спецификации производится статическая (во время компиляции) проверка типов (см. раздел 2.6). В то время это было отличием от С, где типы формальных аргументов не задавались в интерфейсе и не проверялись при вызове; □ определения функций обычно выносятся в другое место, чтобы класс больше походил на спецификацию интерфейса, чем на лексический механизм организации исходного кода. Это облегчает раздельную компиляцию функций-членов класса и внешней программы, которая их использует. Поэтому техники компоновки, традиционной для С, достаточно для поддержки С++ (см. раздел 2.5); □ функция new () является конструктором, для компилятора она имеет специальный смысл. Такие функции дают определенные гарантии опюситель-но классов (см. раздел 2.11): конструктор - в то время он назывался new-функцией - обязательно будет вызван для инициализации каждого объекта своего класса перед его первым использованием; Эффективность исполнения □ имеются указательные и неуказательные типы (они есть и в С, и в Simula). Указатели могут указывать на объекты как встроенных, так и пользовательских типов; □ как и в С, память для объектов может выделяться тремя способами: в стеке (автоматическая память), по фиксированному адресу (статическая память) и из кучи (динамическая память). Но в отличие от С, в С with Classes есть специальные операторы new и delete для выделения и освобождения ди-намическо!! памяти (см. раздел 2.11.2). Дальне11шую разработку С with Classes и С++ можно в значительно!! мере рассматривать как изучение последстви!! этих проектных решени!!, выявление их положительных и отрицательных сторон и устранение проблем, вызванных недостатками. Многие, но далеко не все следствия принятых решени!! были понятны уже в то время (работа [Stroustrup,1980] датирована 3 апреля 1980 г.). В данном разделе я попытаюсь объяснить, чту же было ясно уже тогда, и сошлюсь на разделы, где рассматриваются более отдаленные последствия и позд-не11шие реализации. 2.4. Эффективность исполнения в Simula не может быть локальных или глобальных переменных типа класса, память для каждого объекта класса выделяется динамически оператором new. Измерения с помошью кембриджского симулятора убедили меня, что это основная причина неэффективности языка. Позднее Карел Бабчиски (Karel Babcisky) из Норвежского Вычислительного Центра представил данные о производительности Simula, подтвердившие Moii вывод [Babcisky, 1984]. Уже по этой причине я хотел иметь глобальные и локальные переменные типа класса. Кроме того, наличие разных правил для создания и области действия переменных встроенных и пользовательских типов просто неизяшно, а в некоторых случаях я видел, как страдает мой стиль программирования от отсутствия в Simula локальных и глобальных переменных типа класса. Не хватало и возможности иметь указатели на встроенные типы в Simula. Эти наблюдения со временем превратились в определенное правило дизайна С++: пользовательские и встроенные типы должны вести себя одинаково по отношению к правилам языка. И сам язык, и инструментальные средства должны обеспечивать для них одинаковую поддержку. Когда формулировался этот критерий, поддержка встроенных типов была гораздо обширнее, но развитие языка С++ продолжалось, так что теперь данные типы поддержаны чуть хуже пользовательских (см. раздел 15.11.3). В первой версии С with Classes отсутствовали встраиваемые (inline) функции, но вскоре они были добавлены. Основная причина их включения в язык - опасение, что из-за расходов на преодоление зашитного барьера люди не захотят пользоваться классами для сокрытия представления. Так, в [Stroustrup,1982b] отмечено, что многие делают члены классов открытыми, дабы не расплачиваться за вызов конструктора в простых классах, где для инициализации достаточно од-ного-двух присваиваний. Толчком для включения в С with Classes встраиваемых функций послужил проект, в котором для некоторых классов, связанных с обработкой в реальном времени, накладные расходы на вызов функций оказались неприемлемы. Чтобы воспользоваться преимуществами классов в такого рода приложениях, необходимо иметь возможность бесплатно преодолевать защиту доступа. Этого можно было добиться только сочетанием представления в объявлении класса с встраиванием вызовов открытых функций. В связи с этим возникло следующее правило С++: недостаточно просто предоставить возможность, нужно также, чтобы плата за пользование ей была не слишком велика, то есть приемлема на том оборудовании, которое есть у пользователей , а не приемлема для исследователей, имеющих доступ к высокопроизводительному оборудованию или приемлема через пару лет, когда аппаратные средства подешевеют . С with Classes всегда рассматривался как язык, готовый к применению сейчас или в следующем месяце, а не как исследовательский проект, от которого можно ожидать отдачи через несколько лет. 2.4.1. Встраивание Встраивание было признано важным для удобства работы с классами. Поэтому вопрос заключался не в том, стоит ли его реализовывать, а в том, кбк это сделать. Два аргумента убедили меня, что только программист должен решать, какие функции компилятор попытается встроить в код. Во-первых, у меня был печальный опыт работы с языками, где решение вопроса о встраивании оставлялось на усмотрение компилятора, поскольку он якобы лучше знает . Однако на компилятор можно положиться только в том случае, если в него запрограммирована концепция встраивания и его представление об оптимизации по времени и памяти совпадает с моим. Опыт работы с другими языками показал, что встраивание, как правило, будет реализовано в следующей версии , да и то в соответствии с внутренней логикой языка, которой программист не может эффективно управлять. Кроме того, С (а за ним С with Classes, и С++) организует раздельную компиляцию, так что компилятору всегда доступен только небольшой фрагмент всей программы (см. раздел 2.5). Встраивание функции, исходный код которой неизвестен, возможно только при наличии очень развитой технологии компоновки и оптимизации, но тогда такой технологии не было (нет и сейчас в большинстве сред разработки). Наконец, методы, использующие глобальный анализ кода, в частности автоматическое встраивание без поддержки со стороны пользователя, плохо адаптируются к большим программам. С with Classes проектировался для того, чтобы получать эффективный код при наличии простой переносимой реализации на наиболее распространенных системах. С учетом всего вышесказанного от программиста требуется по.мощь. Даже сегодня этот выбор кажется мне правильным. В С with Classes допускалось только встраивание функций-членов. Единственным способом заставить компилятор встроить функцию было помещение ее в объявление класса. Например: class stack { /* ... */ char pop() { if (top <= min) error( стек пуст );
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |