|
Программирование >> Полиморфизм без виртуальных функций в с++
Правила поддержки проектирования и система контроля типов в особенности должны быть более выразительными, чем в предшествующих языках общего назначения. Все возможности должны быть адекватны. Недостаточно просто дать пользователю некоторое средство или порекомендовать способ решения определенной задачи. Предлагаемый вариант не должен требовать слишком больпшх затрат. В противном случае совет звучит, как издевательство. Конечно, в ответ на вопрос Как лучше всего добраться до Мемфиса? можно посоветовать нанять реактивный самолет, но вряд ли это предложение устроит кого-нибудь, кроме .миллионеров. Любое средство включалось в C-I-+, только когда было невоз.можно добиться той же функциональности с меньшими затратами. Мой опыт показывает, что при наличии выбора .между эффективностью и изящество.м, большинство программистов предпочтет эффективность. Напри.мер, встраиваемые функции были введены для того, чтобы, с одной стороны, уменьшить плату за пересечение границ зап1иты, с другой - чтобы предложить улучшенную альтернативу макросам. Конечно, в идеале средство должно быть одновременно и изящным, и эффективным. Там же, где это не получалось, средство либо не предоставлялось вовсе, либо - если без него нельзя было обойтись - делался выбор в пользу эффективности. Важнее включить полезную возможность, чем предотвращать неправильное использование. Плохие программы можно писать на любом языке. Важно уменьшить шансы на случайное неправильное при.менение тех или иных возможностей. Поэтому я потратил много усилий, чтобы принимаемое по умолчанию поведение конструкций С-ы- было либо разу.мно, либо приводило к ошибке компиляции. Например, по умолчанию проверяются все аргументы функции. По умолчанию же все члены класса являются закрытыми. Однако язык, предназначенный для системного програ.м.мирования, не может помешать решительно настроенному программисту обмануть систему, поэто.му лучше направить силы на разработку средств, помогающих писать хорошие программы, чем не дающих написать плохую програ.мму. Похоже, что со временем програм.мисты обучаются. Это вариант старого лозунга, пришедшего еще из С, - доверяй программисту . Различные способы контроля типов и правила ограничения доступа предназначены для того, чтобы разработчик класса мог ясно выразить, чего он ожидает от пользователей с целью предотвращения случайных ошибок, но никак не с целью воспрепятствовать преднамеренному нарушению (см. раздел 2.10). Поддерживать сборку программ из независимо разработанных частей. Чем сложнее систе.ма, чем больше програм.ма, чем ограниченнее ресурсы, тем более обширная поддержка необходима программистам. При проектировании С-ы- много усилий было потрачено на то, чтобы предоставить поддержку в описанных условиях. Большие и сложные приложения должны составляться из независимых частей, иначе управлять и.ми будет невоз.можно. Для такой цели подойдет все, что позволяет разрабатывать компоненты независимо, а затем использовать их в крупной системе не модифицируя. Эволюция С++ во многом проходила под влиянием этой идеи. Классы работают в данном направлении, а абстрактные классы (см. раздел 13.2.2) явно поддерживают разделение интерфейса и реализации. На само.м деле классы можно использовать для выражения широкого спектра стратегий сопряжения [Stroustrup, 1990Ь]. Исключения позволяют вынести обработку ошибок из библиотеки (см. раздел 16.1), шаблоны обеспечивают композицию с помощью подстановки типов (см. разделы 15.3, 15.6 и 15.8), пространства имен решают проблему непреднамеренного совмещения и.мен (см. раздел 17.2), а идентификация типов во время выполнения отвечает на вопрос, что делать, если истинный тип объекта потерялся где-то в библиотечных функциях (см. раздел 14.2.1). Из утверждения программисту нужна тем большая поддержка, чем крупнее система следует, что для обеспечения эффективности нельзя полагаться лишь на методы оптимизации кода, которые хорошо работают только для сравнительно небольших программ. Стало быть, решение о размещении объекта в памяти должно приниматься на основе отдельно взятой единицы трансляции, а виртуальные функции должны компилироваться в эффективный код без расчета на глобальную оптимизацию, в ходе которой обрабатываются все единицы трансляции. Это верно, даже если в качестве эталона эффективности берется С. Последующая оптимизация возможна на этапе, когда доступна информация обо всей программе. Например, если в программе появляется вызов виртуальной функции, иногда есть возможность (в отсутствии динамического связывания) определить, какая же функция на самом деле вызывается. В таком случае вызов виртуальной функции можно заменить вызовом обычной или даже встроить такой вызов. Существуют компиляторы С++, способные на такое. Однако для генерирования эффективного кода подобные оптимизации не являются необходимыми, их стоит считать лишь дополнительным удобством, когда эффективность исполнения важнее времени компиляции, а от динамического связывания классов можно отказаться. Даже если такая глобальная оптимизация признается неразумной, вызов виртуальной функции все равно можно оптимизировать, когда она вызывается для объекта известного типа; еще в Cfront версии 1.0 была возможность осуществлять это. Поддержка больших систем обсуждается в главе 8. 4.4. Технические правила Следующие правила говорят о способах выражения в С++ (см. табл. 4.4). Таблица 4.4 Технические правила Никаких неявных нарушений статической системы типов Предоставлять для определенных пользователем типов такую же полноценную поддержку, как для встроенных Локальность - это прекрасно Избегать зависимости от порядка Если есть сомнения, выбирать такой вариант средства, которому легче обучить Синтаксис важен, хотя бывает и нелогичным Использование препроцессора должно быть исключено Никаких неявных нарушений статической системы типов. При создании каждого объекта указывается его тип: double, char* или dial buf f er. Если способ использования объекта противоречит его типу, то говорят о нарушении правил контроля типов. Язык, где такие нарушения в принципе невозможны, называется сильно типизированным. Язык, в котором каждое такое нарушение обнаруживается во время компиляции, называется статически сильно типизированным. C-I-+ наследовал от С такие свойства, как объединения (unions), приведение типов и массивы, которые не позволяют обнаружить все нарушения во время ко.м-пиляции. Сегодня в С-ы- не допускаются неявные нарушения правил контроля типов. Это означает: для того чтобы обойти систему, вам придется явно использовать объединение, приведение типов или массив, явно сказать, что аргумент функции не должен контролироваться, или явно воспользоваться небезопасной С-компоновкой. При любом использовании небезопасных средств на этапе компиляции может выдаваться предупреждение. Еще важнее то, что теперь в С-ы-имеются средства, которые удобнее эквивалентных небезопасных средств и не уступают им в эффективности. В качестве примера можно привести производные классы (см. раздел 2.9), стандартный шаблон класса массива (см. раздел 8.5), ти-побезопасную компоновку (см. раздел 11.3) и дина.мически проверяемые приведения типов (см. раздел 14.2). Из-за требований совместимости с С и уважения к сложившейся практике путь к такому положению был длинны.м и тернистым; большинству программистов еще только предстоит усвоить более безопасные приемы. Всюду, где воз.можно, проверки производятся на этапе компиляции. Если что-то нельзя проверить из-за отсутствия необходи.мой информации в данной единице трансляции, прилагаются все усилия, чтобы выполнить проверку во время редактирования связей. Чтобы помочь программисту в ситуации, где компилятор и компоновщик оказались бессильны, имеется механизм идентификации типов во время выполнения (см. раздел 14.2) и исключения (см. главу 16). Предоставлять для определенных пользователем типов такую же полноценную поддержку, как для встроенных. Поскольку определенные пользователем типы - это главное, что есть в программе на С-ы-, поддержка для них должна быть максимально обширной. Поэтому ограничения типа объекты классов могут распределяться только в свободной памяти признаны неприемлемыми. Необходимость в настоящих локальных переменных для таких арифметических типов, как complex, привела к тому, что поддержка для типов, имеющих значение (конкретных типов), оказалась сравни.мой с поддержкой для встроенных типов, а иногда даже превосходящей ее. Локальность - это прекрасно. Весьма желательно, чтобы написанный код был замкнутым за исключением тех случаев, когда е.му нужны внешние сервисы. Хорошо, если за такими сервисами можно обращаться без особых хлопот. И наоборот, когда функции, классы и т.п. предоставляются другим частям программы, хотелось бы не опасаться взаимодействия деталей реализации и постороннего кода. До этих идеалов языку С далеко, как до небес. Любая глобальная функция или глобальная переменная видна компоновщику, поэтому ее имя будет конфликтовать с такими же именами в других частях программы, если только оно явно не объявлено как static. Любое имя может использоваться как имя функции, даже
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |