Главная » Программирование звука » Истинное ООП: классы и наследование

0

Теперь я готов привести пример с буфером, используя обычную для C++ запись:

class Buffer {

private:

char *_begin;

char *_end;

char _data[256] ;

public:

Buffer() { _begin = _data; _end = _data; };

~Buffer () { delete[] _data;}

void Insert (char a) { *_end++ = a ;}

char Remove () {return *_begin++ ; }

};

Здесь я кое-что изменил. Во-первых, использовал слово class вместо struct. Хотя эти два слова почти взаимозаменяемы в C++, по традиции используется слово class для всего, что содержит функции-члены.

Я также добавил спецификаторы видимости. Элементы private: и public: говорят  компилятору,  что  определенные  члены  этого  класса  можно  использовать вне его, а некоторые нет. Заметьте, что я скрыл все члены данных. Это считается хорошим стилем программирования.

Приватные  данные  считаются  хорошим  стилем  программирования,  потому  что они  упрощают  осуществление  изменения  способа  хранения  данных.  Даже  в  таком простом  примере  я  изменил  тип  переменной  _data с  массива  на  указатель  и  добавил  программный  код  для  размещения  буфера  в  куче.  Если  я  провожу  выделение памяти под буфер, мне необходимо гарантировать, что эта память будет освобождена, поэтому я добавил деструктор (destructor). Деструктор имеет то же имя, что  и  класс,  но  оно  начинается  с  тильды  (~Buffer).  Компилятор  вызывает  деструктор  автоматически  в  соответствующие  моменты  времени  (во  время  выполнения оператора delete или в конце блока для автоматических переменных).

Так  как  сам  буфер  является  массивом,  я  использовал  формы  new и  delete,

предназначенные  для  работы  с  массивами.  Запись  new char[256] размещает

в памяти блок из 256 символов; оператор delete [] _data освобождает блок.

Если  вы  выделяете  память  под  массив,  надо  применить  предназначенную   для

данной  форму  записи  оператора  delete для  ее  освобождения.  Некоторые  компиляторы  за  этим  не  следят,  и  вы,  если  будете  невнимательны,  можете  переполнить кучу.

Наследование

Большинство   программ   создаются   постепенно.   Сначала   вы   разрабатываете ядра,   обладающие   базовой   функциональностью,   а   затем   украшаете   программу множеством   небольших   дополнений.   Как   и   все   остальные   объектно-ориентированные  языки,  C++  явным  образом  поддерживает  эту  особенность  через  наследование (inheritance).

Наследование   используется   двумя   способами.   Первый     уточнение   другого класса.  Например,  если  вам  нужен  буфер,  обладающий  некоторыми  дополнительными  возможностями,  уместно  создать  новый  класс  SpecialBuffer,  наследующий от Buffer:

class SpecialBuffer :public Buffer {

// Новые   свойства SpecialBuffer.

}

Запись :public Buffer показывает: SpecialBuffer дeлaeт вce, чтo yмeeт Buffer.  B  частности,  SpecialBuffer автоматически  получает  все  переменныечлены и функции-члены класса Buffer.

Наследование     необыкновенно   полезный   инструмент   при   разработке   про-

граммного  обеспечения.  Например,  при  проектировании  своих  классов-проигрывателей  я  знал,  что  различные  типы  проигрывателей  должны  совместно  обладать определенными  возможностями.  Определив  эти  возможности  один  раз  в  общем базовом  классе  (base  class),  я  смог  упростить  классы  специфичных  проигрывателей.  A  поскольку  существовала  всего  одна  копия  совместно  разделяемого  кода, всю программу было легче отлаживать и поддерживать.

Кроме  того,  наследование  используется  для  улучшения  абстракции  кода.  Определяя  общий  интерфейс  в  базовом  классе,  вы  можете  гарантировать,  что  каждый  класс  будет  применяться  идентично.  B  этом  случае  полезно  определить  базовый класс, даже если он не содержит программного кода.

Виртуальные функции

Обычно,  когда  вы  используете  запись  object.Foo(),  компилятор  обращает внимание на тип переменной object и выбирает нужную функцию Foo() во время компляции. Однако это не всегда то, что вы хотите.

Предположим,  у  вас  есть  несколько  классов  с общим  интерфейсом.  Например, оба моих класса WaveRead и MidiRead наследуют от AudioAbstract, поэтому вы можете вызывать метод GetSamples для любого из них. Ha самом деле, стоит лишь посмотреть на мой код, как станет очевидно, что большая его часть работает только на то, чтобы объект наследовал от AudioAbstract. B таких случаях я использую  указатели  на  AudioAbstract.  Рассмотрим  следующий  небольшой  пример:

MidiRead audioObject ;       // создаем объект MidiRead. AudioAbstract *audioPointer = &audioObject;

audioPointer->GetSamples();  // запрашиваем    отсчеты.

Обычно  компилятор  смотрит  на  тип  переменной  audioPointer.  B  зависимости  от  используемого  типа,  он  преобразует  последнюю  строчку  в  вызов  метода GetSamples класса AudioAbstract. Это не совсем верно. Решение заключается в том, чтобы пометить методы, подобные GetSamples, спецификатором virtual. Объявление  функции  с  таким  спецификатором  заставляет  компилятор  проверять тип  объекта  во  время  выполнения  программы  и  использовать  эту  информацию для   выбора   подлежащего   вызову   метода   GetSamples.   (Виртуальные   методы лишь  немного  менее  эффективны  по  сравнению  с  невиртуальными.  Компилятор строит  таблицу  адресов  функций  для  каждого  класса  и  единожды  осуществляет поиск  по  массиву  для  определения  местонахождения  виртуальной  функции.  Потеря   эффективности   настолько   невелика,   что   у   многих   программистов   вошло в привычку определять любую функцию-член как виртуальную.)

Разумно  используя  виртуальные  функции  и  наследование,  можно  построить семейство  классов,  легко  взаимозаменяемых,  но  в  то  же  время  дающих  совершенно разные функциональные возможности. Например, мой программный код для

MIDI  использует  классы,  которые  наследуют  от  AbstractNote.  Это  позволяет им  применять  множество  методов  инструментного  синтеза  без  каких-либо  изменений программного кода MIDI.

Существует   несколько   небольших   сложностей,   связанных   с   используемой в  C++  формой  наследования.  Одна  заключается  в  том,  что  деструкторы  всегда должны  быть  виртуальными.  B  противном  случае  при  удалении  объекта  может быть  вызван  не  тот  деструктор.  (K  счастью,  спецификатор  virtual наследуется, поэтому достаточно объявить конструктор как virtual в базовом классе.)

По причине тесной связи между некоторыми основными принципами C++ важ-

но  понимать  различия  между  классами  определений  (concrete)  и  абстрактными (abstract)  классами.  Если  быть  кратким,  то  на  основе  первого  вы  создаете  объекты на основе второго вы их никогда не создадите. Например, вы никогда не увидите, чтобы я создавал объект класса AudioAbstract (я определяю много указателей на AudioAbstract, но все они указывают на объекты производных классов). Два  важных  правила  C++  заключаются  в  следующем:  никогда  не  наследовать  от класса определений и никогда не создавать объекты абстрактного класса.

B  моем  коде  абстрактные  классы  как  часть  своего  имени  содержат  слово

Abstract.

Это помогает мне всегда быть уверенным, что я никогда не создаю объекты абстрактно-

го класса и всегда наследую от абстрактного класса.

Есть   способ   подчеркнуть   это   различие,   используя   виртуальные   функции. Если  они  определяются  со  спецификатором  =  0,  компилятор  не  допустит  создания  вами  объекта  данного  класса.  Такие  функции  называются  чистыми  виртуальными   функциями   (pure   virtual   function),   и   многие   определяют   абстрактный класс  как  класс,  содержащий,  по  крайней  мере,  одну  чистую  виртуальную  функцию.  Однако  это  определение  кажется  мне  излишне  ограничивающим  главным образом  потому,  что  абстрактные  классы  и  классы  определений  в  других  объектно-ориентированных   языках   представляют   собой   широкоиспользуемые   концепции проектирования.

Источник: Кинтцель Т.  Руководство программиста по работе со звуком = A Programmer’s Guide to Sound: Пер. с англ. М.: ДМК Пресс, 2000. 432 с, ил. (Серия «Для программистов»).

По теме:

  • Комментарии