Главная · Поиск книг · Поступления книг · Top 40 · Форумы · Ссылки · Читатели

Настройка текста
Перенос строк


    Прохождения игр    
Stoneshard |#11| Battle at the castle
Stoneshard |#10| A busy reaper
The Elder Scrolls IV: Oblivion Remastered - Trash review
Stoneshard |#9| A Million Liches

Другие игры...


liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня
Rambler's Top100
Образование - Страустрап Б. Весь текст 579.17 Kb

Язык С++

Предыдущая страница Следующая страница
1 ... 27 28 29 30 31 32 33  34 35 36 37 38 39 40 ... 50
  }

     7.3.4 Обработка Ошибок

  Есть четыре  подхода к  проблеме, что  же делать,  когда во время
выполнения общецелевое  средство вроде slist сталкивается с ошибкой
(в C++ нет никаких специальных средств языка для обработке ошибок):
  [1] Возвращать недопустимое значение и позволить пользователю его
     проверять;
  [2] Возвращать  дополнительное  значение  состояния  и  разрешить
     пользователю проверять его;
  [3] Вызывать функцию ошибок, заданную как часть класса slist; или
  [4]   Вызывать    функцию   ошибок,    которую   предположительно
     предоставляет пользователь.
  Для   небольшой    программы,    написанной    ее    единственным
пользователем, нет  фактически никаких особенных причин предпочесть
одно  из  этих  решений  другим.  Для  средства  общего  назначения
ситуация совершенно иная.
  Первый подход,  возвращать недопустимое  значение,  неосуществим.
Нет совершенно  никакого способа  узнать, что  некоторое конкретное
значени будет недопустимым во всех применениях slist.
  Второй подход,  возвращать значение состояния, можно использовать
в некоторых  классах (один  из вариантов  этого плана применяется в
стандартных  потоках   ввода/вывода  istream   и  ostream;   как  -
объясняется в  #8.4.2). Здесь,  однако, имеется серьезная проблема,
вдруг пользователь  не позаботится  проверить  значение  состояния,
если средство не слишком часто подводит. Кроме того, средство может
использоваться в  сотнях или  даже тысячах мест программы. Проверка
значения в каждом месте сильно затруднит чтение программы.
  Третьему  подходу,   предоставлять  функцию   ошибок,   недостает
гибкости. Тот, кто реализует общецелевое средство, не может узнать,
как пользователи  захотят, чтобы  обрабатывались ошибки.  Например,
пользователь  может   предпочитать   сообшения   на   датском   или
венгерском.
  Четвертый подход, позволить пользователю задавать функцию ошибок,
имеет некоторую  привлекательность  при  условии,  что  разработчик
предоставляет класс  в виде библиотеки (#4.5), в которой содержатся
стандартные функции обработки ошибок.
  Решения  3   и  4   можно  сделать   более  гибкими  (и  по  сути
эквивалентными), задав указатель на функцию, а не саму функцию. Это
позволит разработчику  такого  средства,  как  slist,  предоставить
функцию ошибок, действующую по умолчанию, и при этом программистам,
которые  будут   использовать  списки,   будет  легко  задать  свои
собственные  функции   ошибок,  если   нужно,  и  там,  где  нужно.
Например:

                             - стр 216 -

  typedef void (*PFC)(char*); // указатель на тип функция
  extern PFC slist_handler;
  extern PFC set_slist_handler(PFC);

Функция   set_slist_hanlder()   позволяет   пользователю   заменить
стандартную   функцию.    Общепринятая   реализация   предоставляет
действующую по  умолчанию функцию обработки ошибок, которая сначала
пишет сообщение  об ошибке в cerr, после чего завершает программу с
помощью exit():

  #include "slist.h"
  #include

  void default_error(char* s)
  {
      cerr << s << "\n";
      exit(1);
  }

Она описывает  также указател  на функцию  ошибок и,  для  удобства
записи, функцию для ее установки:

  PFC slist_handler = default_error;

  PFC set_slist_handler(PFC handler);
  {
      PFC rr = slist_handler;
      slist_handler = handler;
      return rr;
  }

Обратите внимание,  как set_slist_hanlder()  возвращает  предыдущий
slist_hanlder().  Это  делает  удобным  установку  и  переустановку
обработчиков ошибок  на манер  стека. Это  может  быть  в  основном
полезным в больших программах, в которых slist может использоваться
в нескольких  разных ситуациях,  в каждой  из которых  могут, таким
образом, задаваться свои собственные подпрограммы обработки ошибок.
Например:

  {
  PFC old = set_slist_handler(my_handler);

      // код, в котором в случае ошибок в slist
      // будет использоваться мой обработчик my_handler

      set_slist_handler(old); // восстановление
  }

  Чтобы сделать управление более изящным, slist_hanlder мог бы быть
сделан членом  класса slist,  что позволило  бы  различным  спискам
иметь одновременно разные обработчики.

                             - стр 217 -

     7.3.5 Обобщенные Классы

  Очевидно,  можно   было  бы   определить  списки   других   типов
(classdef*, int,  char* и  т.д.) точно  так же,  как был  определен
класс nlist:  простым выводом  из класса slist. Процесс определения
таких новх  типов утомителен  (и  потому  чреват  ошибками),  но  с
помощью макросов  его можно  "механизировать".  К  сожалению,  если
пользоваться стандартным  C препроцессором  (#4.7 и  #с.11.1),  это
тоже может  оказаться тягостным.  Однако полученными  в  результате
макросами пользоваться довольно просто.
  Вот пример  того, как обобщенный (generic) класс slist, названный
gslist, может  быть задан  как макрос. Сначала для написания такого
рода макросов включаются некоторые инструменты из :

  #include "slist.h"

  #ifndef GENERICH
  #include
  #endif

  Обратите  внимание  на  использование  #ifndef  для  того,  чтобы
гарантировать, что   в одной компиляции не будет включен
дважды. GENERICH определен в .
  После  этого  с  помощью  name2(),  макроса  из    для
конкатенации имен, определяются имена новых обобщенных классов:

  #define gslist(type) name2(type,gslist)
  #define gslist_iterator(type) name2(type,gslist_iterator)

  И,    наконец,    можно    написать    классы    gslist(тип)    и
gslist_iterator(тип):

  #define gslistdeclare(type)                              \
  struct gslist(type) : slist {                            \
      int insert(type a)                                   \
          { return slist::insert( ent(a) ); }              \
      int append(type a)                                   \
          { return slist::append( ent(a) ); }              \
      type get() { return type( slist::get() ); }          \
      gslist(type)() { }                                   \
      gslist(type)(type a) : (ent(a)) { }                  \
      ~gslist(type)() { clear(); }                         \
  };                                                       \
                                                           \
  struct gslist_iterator(type) : slist_iterator {          \
      gslist_iterator(type)(gslist(type)& a)               \
          : ( (slist&)s ) {}                               \
      type operator()()                                    \
          { return type( slist_iterator::operator()() ); } \
  }

  \ на конце строк указывает , что следуюшая строка является частью
определяемого макроса.
  С помощью  этого макроса  список указателей  на имя,  аналогичный
использованному раньше классу nlist, можно определить так:

                             - стр 218 -

  #include "name.h"

  typedef name* Pname;
  declare(gslist,Pname); // описать класс gslist(Pname)

  gslist(Pname) nl;      // описать один gslist(Pname)

Макрос declare  (описать) определен в . Он конкатенирует
свои параметры  и вызывает  макрос с  этим именем,  в данном случае
gslistdeclare, описанный выше. Параметр имя типа для declare должен
быть простым  именем. Используемый  метод макроопределения не может
обрабатывать имена типов вроде name*, поэтому применяется typedef.
  Использования вывода  класса гарантирует,  что все частные случаи
обобщенного класса разделяют код. Этот метод можно применять только
для создания  классов объектов  того же  размера  или  меньше,  чем
базовый класс, который используется в макросе. gslist применяется в
#7.6.2.

     7.3.6 Ограниченные Интерфейсы

  Класс slist - довольно общего характера. Иногда подобная общность
не требуется  или даже  нежелательна.  Ограниченные  виды  списков,
такие как  стеки и  очереди, даже  более обычны, чем сам обобщенный
список. Такие  структуры данных  можно задать,  не  описав  базовый
класс как открытый. Например, очередь целых можно определить так:

  #include "slist.h"

  class iqueue : slist {
                 //предполагается sizeof(int)<=sizeof(void*)
  public:
      void put(int a) { slist::append((void*)a); }
      int det()       { return int(slist::get()); }
      iqueue()        {}
  };

При таком выводе осуществляются два логически разделенных действия:
понятие списка ограничивается понятием очереди (сводится к нему), и
задается тип  int, чтобы  свести  понятие  очереди  к  типу  данных
очередь  целых,   iqueue.  Эти   два  деяствия  можно  выполнять  и
раздельно. Здесь  первая часть  - это список, ограниченный так, что
он может использоваться только как стек:

  #include "slist.h"

  class stack : slist {
  public:
      slist::insert;
      slist::get;
      stack() {}
      stack(ent a) : (a) {}
  };

который потом  используется для  создания типа  "стек указателей на
символы":

                             - стр 219 -

  #include "stack.h"

  class cp : stack {
  public:
      void push(char* a) { slist::insert(a); }
      char* pop() { return (char*)slist::get(); }
      nlist() {}
  };

     7.4 Добавление к Классу

  В предыдущих  примерах производный  класс ничего  не  добавлял  к
базовому  классу.  Для  производного  класса  функции  определялись
только чтобы  обеспечить преобразование  типа.  Каждый  производный
класс просто  задавал альтернативный  интерфейс к  общему множеству
программ.  Этот  специальный  случай  важен,  но  наиболее  обычная
причина определения новх классов как производных классов в том, что
кто-то хочет  иметь то,  что предоставляет  базовый класс, плюс еще
чуть-чуть.
  Для  производного   класса  можно  определить  данные  и  функции
дополнительно к  тем, которые  наследуются из  его базового класса.
Это дает  альтернативную стратегию  обеспечить средства  связанного
списка. Заметьте,  когда в  тот slist,  который  определялся  выше,
помещается элемент,  то создается  slink, содержащий два указателя.
На их  создание тратится  время, а  ведь без  одного из  указателей
можно обойтись,  при условии,  что нужно  только чтобы  объект  мог
находиться в  одном списке.  Так что  указатель next  на  следующий
можно поместить  в сам  объект, вместо  того, чтобы  помещать его в
отдельный объект  slink. Идея  состоит в  том, чтобы  создать класс
olink с  единственным полем  next, и  класс  olist,  который  может
обрабатывать указателями  на такие звенья olink. Тогда olist сможет
манипулировать объектами  луюого  класса,  производного  от  olink.
Буква "o"  в названиях  стоит для  того, чтобы  напоминать вам, что
объект может находиться одновременно только в одном списке olist:

  struct olink {
      olink* next;
  };

Класс olist  очень напоминает  класс slist.  Отличие состоит в том,
что пользователь  класса olist  манипулирует объектами класса olink
непосредсвенно:

  class olist {
      olink* last;
  public:
      void insert(olink* p);
      void append(olink* p);
      olink* get();
      // ...
  };

Мы можем вывести из класса olink класс name:

                             - стр 220 -

  class name : public olink {
      // ...
  };

  Теперь легко  сделать  список,  который  можно  использовать  без
накладных расходов времени на размещение или памяти.
  Объекты, помещаемы  в olist,  теряют свой  тип. Это означает, что
компилятор знает  только то,  что они olink'и. Правильный тип можно
восстановить с помощью явного преобразования типа объектов, вынутых
из olist. Например:

  void f()
  {
      olist ll;
      name nn;
      ll.insert(&nn);              // тип &nn потерян
      name* pn = (name*)ll.get();  // и восстановлен
  }

Другой способ:  тип можно  восстановить, выведя  еще один  класс из
olist для обработки преобразования типа:

  class olist : public olist {
      // ...
      name* get() { return (name*)olist::get(); }
  };

Предыдущая страница Следующая страница
1 ... 27 28 29 30 31 32 33  34 35 36 37 38 39 40 ... 50
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 
Комментарии (4)

Реклама