Анимация
JavaScript
|
Главная Библионтека сработает для некоторых типов с данным компилятором, нет никакой гарантии, что этот метод будет продолжать работать для других типов или с теми же типами в другой версии компилятора. Более подробно этот вопрос освещен в [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о {CleanupC);} Первая классическая механическая ошибка - myunion небезопасно копировать, поскольку программист позабыл предоставить копирующий конструктор и оператор копирующего присваивания. myunion разработан таким образом, что в его конструкторе и деструкторе выполняются некоторые специальные действия, так что данные функции приходится писать самому (генерируемые компилятором функции не подходят). Это корректно, однако недостаточно, поскольку аналогичные действия должны выполняться и в копирующем конструкторе и операторе копирующего присваивания, которые автор явно не предоставил. Это плохо, поскольку операции копирования, сгенерированные компилятором по умолчанию, будут работать неправильно, а именно - они просто побито-во скопируют содержи.мое массива символов, что, скорее всего, приведет к неудовлетворительным результатам, в большинстве случаев - просто к порче содержимого памяти. Рассмотрим следующий код. пример 36-3: копировать myunion небезопасно { myunion ul, u2; ul.getstringC) = "Hello, world"; u2 = ul; побитовое копирование ul в u2 } неприятности: двойное удаление одной и той же строки (если даже считать, что побитовое копирование имеет смысл) > Рекомендация Не забывайте о правиле Большой Тройки [Cline99]; если классу требуется пользовательский копирующий конструктор, оператор копирующего присваивания или деструктор, то, скорее всего, все они нужны ему одновременно. Заканчивая на этом с механическими ошибками, перейдем к паре классических стилистических ошибок. enum uniontype {none, int, LiST,.STRING}; uniontype currtype; inline int& getintQ; inline LIST& getlistC): inline STRINGS getstringC); В этом фрагменте две стилистические ошибки. Во-первых, разрабатываемая структура лишена возможности повторного использования из-за жестко закодированных конкретных типов. На самом деле в исходной статье рекомендуется выполнять такое кодирования всякий раз, когда эго потребуется. Во-вторых, даже при такой преднамеренно ограниченной полезности код не слишком хорошо поддается расширению или сопровождению. Мы вернемся к этому вопросу чуть позже. > Рекомендация Избегайте жесткого кодирования информации, что без нужды делает код более хрупким и ограничивает его гибкость. Имеются также две механические проблемы. Первая заключается в том, что член currtype открыт без существенных на то причин; это нарущает принцип инкапсуляции и означает, что любой пользователь может внести путаницу во флаги, пусть даже ненамеренно. Вторая механическая проблема связана с именами, и с п ол ьзо ва н н ы м и в перечислении; об этом я скажу позже, в отдельном подразделе. protected: Здесь мы сталкиваемся со второй механической ошибкой; внутреннее устройство класса должно быть закрытым, а не защищенным. Единственная причина, по которой оно может быть защищенным, - это необходимость обеспечить доступ со стороны производных классов. Что касается данного случая, то лучше не иметь никаких производных классов, так как наследовать myunion небезопасно по целому ряду причин -- хотя бы из-за странных игр с внутренним устройством класса и отсутствия виртуального деструктора. > Рекомендация Всегда делайте все члены-данные закрытыми. Единственным исключением является случай структур в стиле С, которые не предназначены для инкапсулирования и все члены которых являются открытыми. union { int i; unsigned char buff[max(sizeof(LIST),sizeof(STRING))]; } U; void cleanupO; Ha этом оканчивается определение главного класса. Пойдем дальше и рассмотрим три параллельные функции доступа. inline int& MYUNION : :getintO if( currtype== lNT ) { return u.i; } else { cleanup О; currtype= iNT; return u.i; } else inline LI5T& MYUNIONget!iSt О if( currtype==„LiST ) { return *(reinterpret cast<Ll5T*>(U.buff)); } else { cleanupO; list* ptype = new(u.buff) listO; currtype=„LiST; return *ptype; } else inline STRINGS MYUNION::getstring О ifС currtype== STRiNG) { return *(reinterpret cast<STRiNG*>(u.buff)); } else { cleanup О; STRING* ptype = new(U.buff) STRING О; 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 [ 72 ] 73 74 75 76 77 78 79 80 81 82 83 84 85 |