    В строках 14-19 из абстрактного класса TSet выводится новый класс TMySet. 
Конструктор нового класса не выполняет никаких дополнительных действий, он 
просто вызывает конструктор базового класса TSet(). Более существенно то, что 
в строке 16 объявляется виртуальная функция-член CompareElems(), объявленная 
чистой виртуальной функцией в классе TSet. 
    Пропустим все до конца листинга, где приводится реализация функции-члена 
CompareElems(). Эта функция вызывает библиотечную функцию stricmp() для 
сравнения двух строк. Доступ к этим строкам осуществляется путем обращения к 
функции-члену GetStringO класса TElem (см. также строку 11). При вызове в 
проке 51 HasElem() эта функция-член вызывает реализованную в строках 58-61 
CompareElems(). Вы можете запустить получившуюся программу в Turbo Debugger и 
проанализировать вызов этой функции для наблюдения за тем, как программа 
переадресует CompareElemsO. 
    Данный пример использования виртуальных функций-членов и абстрактных 
классов приводит к нескольким интересным наблюдениям. 
    •   Модуль SET можно скомпилировать заранее и сохранить в библиотеке 
классов. Пользователям не нужен файл SET.CPP с исходным текстом класса TSet. 
Основной программе необходим лишь заголовочный файл SET.H. 
    •  Другая программа может использовать класс TSet для хранения множества 
различных классов объекта TElem. He нужно повторно компилировать SET.CPP для 
каждого нового использования класса. Класс TSet обеспечивает лишь некоторые 
рудиментарные операции над множеством. Пользователю класса предоставляется 
реализация прочих деталей. 
    •   И наконец, при проектировании абстрактных классов, подобных TSet, 
программисты обычно помещают в класс множество виртуальных функций-членов на 
тот случай, если они когда-нибудь понадобятся. При создании абстрактных 
классов постарайтесь обойтись минимумом необходимых членов, вместо того чтобы 
пытаться охватить возможно большую область применения. Если ваши классы будут 
слишком сложны, другие программисты (и даже впоследствии вы сами) могут быть 
обескуражены перспективой вывода новых классов из ваших.	

    Виртуальные деструкторы     

    Функции-члены и деструкторы, но не конструкторы, могут быть виртуальными. 
Виртуальные деструкторы обычно применяются в случаях, когда в некотором классе 
необходимо удалить объекты производного класса, на которые ссылаются указатели 
на базовый класс. Например, рассмотрим следующий класс, который может 
запоминать строковое значение: 

    class TBase { 
    private: 
       char *sp1; 
    public: 
       TBase(const char *s) 
           { sp1 = strdup(s);   }	
    virtual ~TBase() 
        { delete sp1;   } 
    };

    Конструктор класса TBase выделяет пространство для строки путем обращения 
к функции strdupO и сохраняет адрес новой строки в указателе sp1. Виртуальный 
деструктор освобождает эту память, когда объект типа TBase выходит из области видимости. 
    Далее, выведем новый класс из TBase: 

    class TDerived:  public TBase { 
    private: 
       char *sp2; 
    public: 
       TDerived(const char *s1,  const char *s2):  TBase(s1) 
            { sp2 = strdup(s2);   } 
       virtual ~TDerived() 
          { delete sp2;   } 
    }; 

    Новый класс сохраняет вторую строку, на которую ссылается указатель sp2. 
Новый конструктор вызывает TBase(), передавая строку в базовый класс, а также 
выделяет дополнительно немного памяти для второй копии строки, удаляемой 
деструктором класса. 
    Когда объект TDerived выходит из области действия, важно, чтобы обе копии 
строки были удалены. Предположим, вы объявили указатель на класс TBase, но 
присвоили ему адрес объекта TDerived — это вполне допустимо, поскольку, как 
вам уже известно, указатель на базовый класс может ссылаться на объект этого 
класса или на объект любого производного класса. Программа на этой стадии 
выглядит следующим образом: 


    C++     Глава 14. Объектно-ориентированное программирование 359 
