Программирование >>  Арифметические и логические операции 

1 ... 20 21 22 [ 23 ] 24 25 26 ... 53


Другой пример разумного использования 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;



1 ... 20 21 22 [ 23 ] 24 25 26 ... 53

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика