|
Программирование >> Обобщенные обратные вызовы
} Будет ли объект s уничтожен? должен ли он уничтожаться? А объект р? Как указывают приведенные комментарии, если бы такой код был разрешен, серьезных проблем не удалось бы избежать. Чтобы избежать ненужного усложнения языка, проблематичные операции были просто запрещены. Но не думайте, что объединения -- просто рудимент. Наиболее полезны объединения, пожалуй, для экономии памяти, поскольку позволяют данным перекрываться, и это остается немалым преимуществом С++ даже сегодня. Например, некоторые из наиболее продвинугых реализаций стандартной библиотеки С++ используют их для оптимизации малых строк , которая позволяет повторно использовать память внутри самого объекта. Вот основная идея этой оптимизации: в случае строк большого размера объект хранит обычный указатель на динамически выделенную память и дополнительную информацию, такую как, например, размер буфера. Однако эту память, использующуюся для указателя и дополнительной информации, в случае малых строк можно использовать непосредственно для хранения строки, избегая таким образом динамического распределения памяти. Дополнительную информацию об оптимизации малых строк (и других способах оптимизации) можно найти в Suttcrt)2) (задачи 7.2-7.5 в русском издании); см. также обсуждение современных коммерческих реализаций std: :string в [MeyersOl]. Построение объединений 3. В статье [Мап1еу02] рассматривается написание языка сценариев, в котором используются объединения. Допустим, что вы хотите, чтобы ваш язык поддерживал единый тип для переменных, которые в разные моменты времени могут хранить целые числа, строки или списки. Создание union {int i; list<int> 1; string s;}; не работает по причинам, которые разбираются в вопросах 1 и 2. Приведенный далее код представляет собой обходной путь, который пытается обеспечить поддержку произвольного типа в качестве участника объединения (более подробную информацию по этому вопросу вы сможете найти в указанной статье.) С одной стороны, в цитированной статье автором успешно решена реальная проблема. К сожалению, с кодом дела обстоят не вполне благополучно. Проблемы, свя занные с исходным текстом в статье, можно разбить на три основные категории: законность, безопасность и мораль. Отрецензируйте предложенный код и найдите: а) механические ошибки, такие как неверный синтаксис или непереносимые соглашения; б) стилистические улучшения, которые могут повысить ясность исходного текста, его повторное использование и сопровождение. Первый комментарий, который следует сделать по поводу приведенного фрагмента, заключается в том, что основная идея, лежащая в основе данного решения, не законна с точки зрения стандарта С+ + . Вот как описана основная идея в статье: Идея заключается в том, что вместо объявления членов объекта мы объявляем просто буфер {не динамически, а в виде массива типа char в объекте, который должен выступать в роли объединения] и создаем необходимые объекты на лету [путем размещающего конструирования]. - [Мап1еу02] Идея распространенная, но, к сожалению, нездоровая. Выделение буфера одного типа с последующим использованием его для размещения объекта другого типа не соответствует стандарту и не переносимо, поскольку для буфера, который не выделен динамически (т.е. не выделен при помощи функции malloc или оператора new), не гарантируется корректное выравнивание для любого другого типа, кроме изначально объявленного. Даже если эта методика случайно и сработает для некоторых типов с данным компилятором, нет никакой гарантии, что этот метод будет продолжать работать для других типов или с теми же типами в другой версии компилятора. Более подробно этот вопрос освещен в [SutterOO] (задача 4.5 в русском издании), в особенности обратите внимание на врезку Безответственна оптимизация и ее вред . Кроме того, вопросы выравнивания рассматриваются также в [AlexandrescuOl]. При работе над стандартом С++Ох комитет по стандартизации языка рассматривает возможность добавления средств для управления выравниванием в стандарт, в частности, чтобы обеспечить возможность использования методик, подобных описанной, которые опираются на выравнивание, но пока что это вопрос будущего. Пока же, чтобы этот способ работал более-менее надежно хотя бы некоторое время, надо сделать что-то из перечисленного; воспользоваться хакерским приемом max a1ign (см. примечание в статье [Мап1еу02] или поищите max a1ign при помощи Google); воспользоваться нестандартными расширениями наподобие alignof из Gnu С++, для того чтобы код надежно работал с компилятором, поддерживающим такое расширение. (Хотя Gnu и предоставляет макрос alignof, предназначенный для надежной работы на других компиляторах, он также является хакерской уловкой.) Обойти эти неприятности можно путем динамического выделения массива при помощи функции malloc или оператора new, которые гарантируют, что буфер char будет выровнен таким образом, что в нем может быть размещен объект любого типа, но это тоже не самая лучшая идея, поскольку такие действия небезопасны с точки зрения типов, а кроме того, это приводит к снижению эффективности - а именно вопросы эффективности и были основной мотивацией описанной в статье разработки. Альтернативным корректным решением могло бы быть использование boost: :апу (см. ниже), которое хотя и приводит к аналогичному снижению эффективности из-за динамического выделения памяти и косвенного обращения, но, по крайней мере, безопасно и корректно. Попытки действовать против правил языка или заставить его работать не так, как он должен, а как того хочется нам, очень сомнительны и должны быть окружены красными флажками. В упомянутой врезке из книги [SutterOOj я писал, что всякие необычности до добра не доводят. Да, возможны ситуации, когда вполне разумно использовать некоторую непереносимую конструкцию, которая гарантированно работоспособна в данной конкретной среде (в нашем случае, вероятно, можно использовать хак max align), но даже в этом случае следует явно указать нестандартность решения и не использовать его в коде, рекомендованном для широкого использования. Разбор кода Давайте теперь поближе познакомимся с кодом. #1 nclude <list> #1 nclude <string> #include <iostream> using namespace std; Всегда включайте все необходимые заголовочные файлы. Поскольку ниже используется new, следует также включить #include <new>. (Примечание: заголовочный файл <iostream> включен совершенно правильно, так как в исходном авторском тексте выполнялась проверка работоспособности (не вошедшая в данную книгу) разработанного кода с использованием потоков ввода-вывода.) #define tnax(a.b) Ca)>(b)?(a) : (b) typedef list<int> LIST; typedef string STRING; struct myunion { myunion 0 : currtype( none ) {} -MYUNION о {cleanupO;} Первая классическая механическая ошибка - myunion небезопасно копировать, поскольку программист позабыл предоставить копирующий конструктор и оператор копирующего присваивания. myunion разработан таким образом, что в его конструкторе и деструкторе выполняются некоторые специальные действия, так что данные функции приходится писать самому (генерируемые компилятором функции не подходят). Это корректно, однако недостаточно, поскольку аналогичные действия должны выполняться и в копирующем конструкторе и операторе копирующего присваивания, которые автор явно не предоставил. Это плохо, поскольку операции копирования, сгенерированные компилятором по умолчанию, будут работать неправильно, а именно - они просто побито-во скопируют содержимое массива символов, что, скорее всего, приведет к неудовлетворительным результатам, в большинстве случаев - просто к порче содержимого памяти. Рассмотрим следующий код. пример 36-3: копировать MYUNION небезопасно { myunion ul, u2; ul.getstringO = Hello, world ; u2 = ul; побитовое копирование ul в u2 } неприятности: двойное удаление одной и той же строки (если даже считать, что побитовое копирование имеет смысл) > Рекомендация Не забывайте о правиле Большой Тройки [Cline99]; если классу требуется пользовательский копирующий конструктор, оператор копирующего присваивания или деструктор, то, скорее всего, все они нужны ему одновременно. Заканчивая на этом с механическими ошибками, перейдем к паре классических стилистических ошибок. enutn uniontype {none, int, LiST,.string}; uniontype currtype ; inline int& getint(); inline LIST& getlistO: inline STRINGS getstring(); В этом фрагменте две стилистические ошибки. Во-первых, разрабатываемая структура лишена возможности повторного использования из-за жестко закодированных конкретных типов. На самом деле в исходной статье рекомендуется выполнять такое кодирования всякий раз, когда эго потребуется. Во-вторых, даже при такой преднамеренно ограниченной полезности код не слишком хорошо поддается расширению или сопровождению. Мы вернемся к этому вопросу чуть позже. > Рекомендация Избегайте жесткого кодирования информации, что без нужды делает код более хрупким и ограничивает его гибкость.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |