|
Программирование >> Оптимизация возвращаемого значения
Другими словами, следующий код допустим: class string { Из стандартной библиотеки С++, public: -string(); class В { . . . }; Нет элементов данных с деструкторами, виртуальный деструктор не нужен. но в производном от в классе ситуация меняется: class D: public В { string name; теперь В должен быть виртуальньм. И снова небольшое изменение в способе использования класса в (в данном случае добавление производного класса, содержащего член с деструктором) может привести к перекомпиляции и перекомпоновке во всех клиентах. Но небольшие изменения в программе должны иметь небольшое влияние на систему. Эта разработка не проходит данный тест. Тот же автор пишет: Если в иерархии множественного наследования есть деструкторы, то каждый базовый класс должен иметь виртуальный деструктор . Обратите внимание, что во всех этих цитатах авторы говорят в настоящем времени. Как пользователи работают с указателями сейчас? Какие члены класса сейчас имеют деструкторы? Какие классы в иерархии сейчас имеют деструкторы? Размышления в будущем времени ведутся совсем по-другому. Вместо того чтобы задаваться вопросом, как класс используется сейчас, спрашивается, для чего класс разработан? Если класс разработан с целью сделать его базовым (даже если он сейчас и не используется в качестве такового), он должен иметь виртуальный деструктор. Такие классы корректно ведут себя и сейчас, и в будущем, и они не влияют на других пользователей библиотеки при создании новых производных от них классов. (По меньшей мере в том, что касается их деструктора. Если в класс нужно внести еще какие-то изменения, это может оказать влияние на других пользователей класса.) Коммерческая библиотека классов (созданная до введения спецификации string в стандартную библиотеку С++) содержит класс строк без виртуального деструктора. Как это объясняют разработчики? Мы не сделали деструктор виртуальным потому, что не хотели, чтобы класс String содержал таблицу виртуальных функций. Мы не намерены когда-либо использовать String*, хотя знаем о том, какие затруднения это может вызвать. Рассуждая так, думали ли они о будущем или только о настоящем? Конечно же, использование таблицы виртуальных функций технически допустимо (см. правило 24). Реализация большинства классов String содержит внутри каждого объекта String единственный указатель char*, поэтому добавление к каждому объекту String виртуального указателя удвоит размер объектов. Легко понять, почему разработчикам программного обеспечения хочется избежать этого, особенно для таких часто использующихся классов, как String. Производительность подобного класса может снизиться на 20% (см. правило 16). Но обычно память, выделенная под весь объект строки - память под сам объект плюс динамическая память для хранения значения строки - намного больше, чем пространство, необходимое для хранения указателя char*. С этой точки зрения накладные расходы на создание виртуального указателя не столь существенны. Тем не менее, это допустимое техническое соображение. (Комитет стандартизации ISO/ANSI, похоже, тоже так думает: стандартный тип string имеет невиртуальный деструктор.) Меня больше беспокоит замечание разработчиков: Мы не намерены когда-либо использовать String* . Это может быть так, но класс String является частью библиотеки, к которой будут обращаться тысячи программистов. Каждый из них имеет различный опыт работы с С++, и все они делают разные вещи. Все ли они понимают, к чему приводит отсутствие виртуального деструктора в классе String? Знают ли они, что из-за этого создание производных от String классов является весьма рискованным предприятием? Уверены ли разработчики класса, что его пользователи поймут, что удаление объектов при помощи указателей String* будет выполняться неправильно и операции RTTI над указателями и ссылками на объекты типа string будут возвращать некорректные данные? Легко ли использовать этот класс правильно, и сложно ли сделать что-то не так? Разработчики должны ясно указать в документации, что класс String не предназначен для создания производных от него классов, но вдруг программисты не обратят внимания на это предупреждение или просто не будут читать документацию? Альтернативой было бы запретить создание производных классов при помощи средств языка С++. Правило 26 описывает, как можно сделать это, ограничив создание объектов в куче и затем используя для работы с объектами в куче объекты auto ptr. Но тогда интерфейс для создания объектов String был бы непривычным и неудобным, поскольку требовал бы записи: auto ptr<String> ps{String: :makeString( Future tense С++ )) ; ... ps можно рассматривать как указатель на объект String, который не нужно удалять. вместо: string s( Future tense С++ ); Но, возможно, уменьшение риска неправильного поведения производных классов стоило бы такого синтаксического неудобства. (Для класса String это маловероятно, но для других классов подобный компромисс может быть выгодным.) Конечно же, нужно думать и о настоящем. Проектируемые вами программы должны работать на существующих компиляторах; вы не можете ждать, пока будут реализованы последние свойства языка. Программы должны выполняться на поддерживаемом оборудовании и на всех конфигурациях пользователей; вы не можете заставлять пользователей выполнять обновление оборудования или операционной среды. Ваши разработки должны иметь приемлемую производительность абсгрг Предположим, что вы работаете над программным проектом, в котором определены классы животных (Animal). При этом большинство типов животных обрабатывается одинаково, но два класса - Li sard (ящерицы) и Chicken (куры) - требуют специальной обра- ботки. В этом слу- чае, очевидно, классы ящериц, кур и всех остальных жи- ([AniniaP вотных должны быть связаны между собой так, как предс- тавлено на рис. 6.1. Класс Animal включает в себя свойства, общие для всех животных, а классы Lizard и Chicken адаптируют Рис. 6.1 класс Animal соответственно для работы с типами яще- рицы и куры : Вот набросок определений для этих классов: class Animal { public: AnimalSc operator={const Animal& rhs); сейчас; обещания сделать программу более быстрой и компактной через несколько лет обычно не греют сердца потенциальных пользователей. Обычно программное обеспечение, над которым вы трудитесь, должно быть доступно сегодня , что часто означает несколько дней назад . Это важные ограничения, и игнорировать их нельзя. Вот несколько советов, которые наверняка вам пригодятся: □ создавайте полные классы, даже если пока используются только их части. Тогда при возникновении новых требований к классам вам с меньшей вероятностью придется возвращаться к ним снова и модифицировать их; □ разрабатывайте интерфейсы так, чтобы облегчить выполнение обычных операций и предотвратить появление типичных ошибок. Должно быть легко использовать классы правильно, и сложно - неправильно. Запрещайте, например, копирование и присваивание для классов, в которых эти операции бессмысленны. Предотвращайте частичное присваивание (см. правило 33); □ обобщайте код, если это не приводит к большим затратам. Например, если вы пишете алгоритм для обхода дерева, подумайте, нельзя ли обобщить его так, чтобы он мог обрабатывать все типы ориентированных графов без циклов. Думая о будущем, вы увеличите шансы своих программ на повторное использование, облегчите его поддержку, сделаете его более устойчивым и в то же время изменяемым. Сегодняшние и будущие требования должны быть уравновешены. Но слишком много программистов фокусируют свое внимание только на текущих потребностях, и, делая это, они приносят в жертву долгосрочную жизнеспособность разрабатываемых и реализуемых ими программ. Будьте другим. Программируйте, заглядывая в будущее. Правило 33. Делайте нетерминальные классы )актными
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |