С++ для начинающих

       

Деструкторы


Когда заканчивается время жизни объекта производного класса, автоматически вызываются деструкторы производного и базового классов (если они определены), а также деструкторы всех объектов-членов. Например, если имеется объект класса NameQuery:

NameQuery nq( "hyperion" );

то порядок вызова деструкторов следующий: сначала деструктор NameQuery, затем деструктор string для члена _name и наконец деструктор базового класса. В общем случае эта последовательность противоположна порядку вызова конструкторов.

Вот деструкторы нашего базового Query и производных от него (все они объявлены открытыми членами соответствующих классов):

inline Query::

~Query(){ delete _solution; }

inline NotQuery::

~NotQuery(){ delete _op; }

inline OrQuery::

~OrQuery(){ delete _lop; delete _rop; }

inline AndQuery::

~AndQuery(){ delete _lop; delete _rop; }

Отметим два аспекта:

  • мы не предоставляем явного деструктора NameQuery, потому что никаких специальных действий по очистке его объекта предпринимать не нужно. Деструкторы базового класса и класса string для члена _name вызываются автоматически;
  • в деструкторах производных классов оператор delete применяется к указателю типа Query*. Чтобы вызвать не деструктор Query, а деструктор класса того объекта, который фактически адресуется этим указателем, мы должны объявить деструктор базового Query виртуальным. (Более подробно о виртуальных функциях вообще и о виртуальных деструкторах в частности мы поговорим в следующем разделе.)
  • В нашей реализации неявно подразумевалось, что память для операндов, указатели на которые имеются в объектах классов NotQuery, OrQuery и AndQuery, выделена из хипа. Именно поэтому в деструкторах мы применяли к этим указателям оператор delete. Но язык не позволяет обеспечить истинность такого предположения, так как в нем нет различий между адресами в хипе и вне его. С этой точки зрения наша реализация не застрахована от ошибок.

    В разделе 17.7 мы инкапсулируем выделение памяти и конструирование объектов иерархии Query в управляющий класс UserQuery. Это гарантирует выполнение нашего предположения. На уровне программы в целом следует перегрузить операторы new и delete для классов иерархии. Например, можно поступить следующим образом. Оператор new устанавливает в объекте флажок, говорящий, что память для него выделена из хипа. Перегруженный оператор delete проверяет этот флажок: если он есть, то память освобождается с помощью стандартного оператора delete.


    Упражнение 17.7

    Идентифицируйте конструкторы и деструкторы базового и производных классов для той иерархии, которую вы выбрали в упражнении 17.2 (раздел 17.1).

    Упражнение 17.8

    Измените реализацию класса OrQuery так, чтобы он был производным от BinaryQuery.

    Упражнение 17.9

    Найдите ошибку в следующем определении класса:

    class Object {

    public:

       virtual ~Object();

       virtual string isA();

    protected:

       string _isA;

    private:

       Object( string s ) : _isA( s ) {}

    };

    Упражнение 17.10

    Дано определение базового класса:

    class ConcreteBase {

    public:

       explicit ConcreteBase( int );

       virtual ostream& print( ostream& );

       virtual ~Base();

       static int object_count();

    protected:

       int _id;

       static int _object_count;

    };

    Что неправильно в следующих фрагментах:

    (a) class C1 : public ConcreteBase {

        public:

           C1( int val )

             : _id( _object_count++ ) {}

           // ...

        };

    (b) class C2 : public C1 {

        public:

           C2( int val )

             : ConcreteBase( val ), C1( val ) {}

           // ...

        };

    (c) class C3 : public C2 {

        public:

           C3( int val )

             : C2( val ), _object_count( val ) {}

           // ...

        };

    (d) class C4 : public ConcreteBase {

        public:

           C4( int val )

             : ConcreteBase ( _id+val ){}

           // ...

        };

    Упражнение 17.11

    В первоначальном определении языка C++ порядок следования инициализаторов в списке инициализации членов определял порядок вызова конструкторов. Принцип, который действует сейчас, был принят в 1986 году. Как вы думаете, почему была изменена исходная спецификация?


    Содержание раздела