|
Программирование >> Арифметические и логические операции
Другой пример разумного использования goto следующий: int foo() int res; if(... ) { res = 10; goto finish; finish: return res; Понятно, что без goto это выглядело бы как return 10 внутри if. Итак, в чем преимущества такого подхода. Ну, сразу же надо вспомнить про концептуальность - у функции становится только один выход , вместо нескольких (быстро вспоминаем про IDEF). Правда, концепту-альность - это вещь такая... Неиспользование goto тоже в своем роде концептуальность, так что это не показатель (нельзя противопоставлять догму догме, это просто глупо). Тем не менее, выгоды у такого подхода есть. Во-первых, вполне вероятно, что перед возвратом из функции придется сделать какие-то телодвижения (закрыть открытый файл, например). При этом, вполне вероятно, что когда эта функция писалась, этого и не требовалось - просто потом пришлось дополнить. И что? Если операторов return много, то перед каждым из них появится одинаковый кусочек кода. Как это делается? Правильно, методом cut&paste . А если потом придется поменять? Тоже верно, search&replace . Объяснять, почему это неудобно не будем - это надо принять как данность. Во-вторых, обработка ошибок, которая также требует немедленного выхода с возвратом используемых ресурсов. В принципе, в C++ для этого есть механизм исключительных ситуаций, но когда он отсутствует (просто выключен для повышения производительности), это будет работать не хуже. А может и лучше по причине более высокой скорости. В-третьих, упрощается процесс отладки. Всегда можно проверить что возвращает функция, поставить точку останова (хотя сейчас некоторые отладчики дают возможность найти все return и поставить на них точки останова), выставить дополнительный assert или еще что-нибудь в этом духе. В общем, удобно. Еще goto очень успешно применятся при автоматическом создании кода - читателя исходного текста там не будет, он будет изучать то, по чему исходный текст был создан, поэтому можно (и нужно) допускать различные вольности. В заключение скажем, что при правильном использовании оператор goto очень полезен. Надо только соблюдать здравый смысл, но это общая рекомендация к программированию на С/С++ (да и вообще, на любом языке программирования), поэтому непонятно почему goto надо исключать. Глава 10. Виртуальный конструктор Предупреждение: то, что описано - не совсем уж обычные объекты. Возможно только динамическое их создание и только в отведённой уже памяти. Ни о каком статическом или автоматическом их создании не может быть и речи. Это не цель и не побочный эффект, это расплата за иные удобства. Краткое описание конкретной ситуации, где всё это и происходило. В некотором исследовательском центре есть биохимическая лаборатория. Есть в ней куча соответствующих анализаторов. Железки они умные, работают самостоятельно, лишь бы сунули кассету с кучей материалов и заданиями. Всякий анализатор обрабатывает материалы только определённой группы. Со всех них результаты текут по одному шлангу в центр всяческих обработок и складирования. Масса частностей, но нам они неинтересны. Суть - всякий результат есть результат биохимического анализа. Текущий потоком байт с соответствующими заголовками и всякими телами. Конкретный тип реально выяснить из первых шестнадцати байт. Максимальный размер - есть. Ho он лишь максимальный, а не единственно возможный. He вдаваясь в подробности принятого решения (это вынудит вдаваться в подробности задания), возникла конкретная задача при реализации - уже во время исполнения конструктора объекта конкретный тип результата анализа неизвестен. Неизвестен (соответственно) и его размер. Известен лишь размер пула для хранения некоторого количества этих объектов. Две проблемы - сконструировать объект конкретного типа в конструкторе объекта другого (обобщающего) типа и положить его на это же самое место в памяти. При этом память должно использовать эффективно, всячески минимизируя (главная проблема) фрагментацию пула, ибо предсказать время, в течение которого результат будет оста- ваться нужным (в ожидании, в частности, своих попутчиков от других анализаторов), невозможно. Это - не очередь (иначе всё было бы значительно проще). Решение: от классической идиомы envelope/letter (которая сама по себе основа кучи идиом) к виртуальному конструктору с особым (либо входящим в состав, либо находящимся в дружеских отношениях) менеджером памяти. Излагается на смеси C+ + и недомолвок (некритичных) в виде . . . : class BCAR { Bio-Chemical Analysis Result friend class BCAR MemMgr; protected: BCAR() { /* Должно быть пусто!!! */ } public: BCAR( const unsigned char * ); void *operator new( size t ); void operator delete( void * ); virtual int size() { return 0; } private: struct { трали-вали } header; Это был базовый класс для всех прочих конкретных результатов анализов. У него есть свой new. Но для реализации идеи используется не дефолтовый new из C++ rtl, а используется следующее: inline void *operator new( size t, BCAR *p ) { return p; Именно за счёт его мы получим in place замену объекта одного класса (базового) объектом другого (производного). Раньше было проще - this допускал присваивание. Теперь - менеджер памяти. class BCAR MemMgr { friend BCAR; public: BCAR MemMgr(); void alloc( int ); void free( BCAR *, int ); BCAR *largest(); private: Это примерный его вид. Он создаётся в единственном экземпляре: static BCAR MemMgr MemoryManager; и занимается обслугой пула памяти под все объекты. В открытом интерфейсе у него всего три функции, назначение alloc/free любому понятно (хотя alloc в действительности ничего не аллоцирует, а делает обрезание того, что даёт largest и соответствующим образом правит списки менеджера), а largest возвращает указатель на самый большой свободный блок. В сущности, она и есть BCAR::new, которая выглядит так: void *BCAR::operator new( size t ) { return MemoryManager.largest(); Зачем самый большой? А затем, что при создании объекта его точный тип ещё неизвестен (ибо создаваться будет через new BCAR), поэтому берём по максимуму, а потом alloc всё подправит. Теперь собственно классы для конкретных результатов. Все они выглядят примерно одинаково: class Phlegm: public BCAR { friend BCAR; private: int size() { retrurn sizeof( Phlegm ); } struct PhlegmAnalysisBody { тут всякие его поля PhlegmAnalysisBody body; Phlegm( const unsigned char *data ): BCAR() { MemoryManager.alloc( size() ); ::memcpy( &body, data + sizeof( header ), sizeof( body ) ); Где тут расположен виртуальный конструктор. А вот он: BCAR::BCAR( const unsigned char *dataStream ) { ::memcpy( &header, dataStream, sizeof( header ) ); if( CRC OK( dataStream ) ) { определяем тип конкретного результата и строим соответствующий объект прямо на месте себя switch( AnalysisOf( dataStream ) ) { case PHLEGM: ::new( this ) Phlegm( dataStream ); break; case BLOOD: ::new( this ) Blood( dataStream ); break; case ... : Теперь, чтобы не вынуждать вас носиться по всему материалу в целях построения целостной картины, объясним происходящее по шагам. Менеджер памяти создан, инициализирован. Пул памяти существует (хотя бы от обычного malloc, а хоть и с потолка - ваше дело). Есть некоторый поток байт (пусть он зовётся stream), в котором то, с чем мы и боремся. Объект создаётся следующим образом: BCAR *analysis = new BCAR( stream ); Обратите внимание - мы создаём объект класса BCAR. В первую очередь вызывается BCAR::new, который в действительности завуалированный MemoryManager.largest(). Мы имеем адрес в свободной памяти, где и создаётся объект BCAR и запускается его конструктор BCAR::BCAR( const unsigned char * ). В конструкторе по информации из заголовка (полученного из потока stream) выясняется точный тип анализа и через глобальный new (который не делает ничего) создаётся на месте объекта BCAR объект уточнённого типа. Начинает исполняться его конструктор, который в свою очередь вызывает конструктор BCAR::BCAR(). Надеемся, стало понятно почему BCAR::BCAR() определяется с пустым телом. Потом в конструкторе конкретного объекта вызывается MemoryManager.alloc( int ), благодаря чему менеджер памяти получает информацию о точном размере объекта и соответствующим образом правит свои структуры. Уничтожение объектов примитивно, ибо всей необходимой информацией MemoryManager располагает: void BCAR::operator delete( void *p ) { MemoryManager.free( (BCAR *)p, ((BCAR *)p)->size() ); private: int bodySize; int size() { return sizeof( Blood ) + bodySize; } int getSize( const char * ); sturct BloodAnalysisBody { тут его поля } *body; Blood( const unsigned char *data ): BCAR() { body = (BloodAnalysisBody *) bodyStorage; bodySize = getSize( data ); ::memcpy( bodyStorage, data + sizeof( header ), bodySize ); MemoryManager.alloc( size() ); unsigned char bodyStorage[ 1 ]; Бороться с данными далее придётся через body->, но сейчас мы не об этом Переносимость этой конструкции очень высока, хотя может понадобиться некоторая правка для весьма экзотических машин. Факты же таковы, что она используется в трёх очень крупных мировых центрах на четырёх аппаратных платформах и пяти операционках. Ho это ещё не всё. Как особо дотошные могли заметить - здесь присутствует виртуальность конструктора, но в любом случае объект конкретного класса всё равно имеет фиксированный размер. А вот объектов одного класса, но разного размера нет. До относительно недавнего времени нас это вполне устраивало, пока не появились некоторые требования, в результате которых нам пришлось сделать и это. Для этого у нас есть два (по меньшей мере) способа. Один - переносимый, но неэстетичный, а второй - непереносимый, но из common practice. Эта самая common practice состоит в помещении последним членом класса конструкции вида unsigned char storage[ 1 ] в расчёте на то, что это будет действительно последним байтом во внутреннем представлении объекта и туда можно записать не байт, а сколько надо. Стандарт этого вовсе не гарантирует, но практика распространения нашего детища показала, что для применяемых нами компиляторов оно именно так и есть. И оно работает. Чуть-чуть поправим наши объекты: class Blood: public BCAR { friend BCAR;
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |