|
Программирование >> Решение нетривиальных задач
Хотя я и не показывал этого прежде, то же самое происходит в деструкторе. Первое, что делает деструктор, - это помещает в vtable указатель на таблицу своего собственного класса. Только после этого он выполняет написанный вами код. Деструктор производного класса вызывает деструктор базового класса на выходе (в самом конце - после того, как выполнен написанный пользователем код). 138. Не вызывайте чисто виртуальные функции из конструкторов Это правило вытекает из только что рассмотренной картины. Определение чисто виртуальной функции (у которой =0 вместо тела) приводит к тому, что в таблицу виртуальных функций базового класса помещается null вместо обычного указателя на функцию. (В случае чисто виртуальной функции нет функции, на которую необходимо указывать). Если вы вызываете чисто виртуальную функцию из конструктора, то используете таблицу базового класса и на самом деле вызываете функцию при помощи указателя NULL. Вы получите дамп оперативной памяти на машине с UNIX и Общая ошибка защиты в системе Windows, но MS-DOS просто исполнит то, что вы просили, и попытается выполнить код по адресу 0, считая его правильным. 139. Деструкторы всегда должны быть виртуальными Рассмотрим такой код: class base char *p; ~base() { p = new char[SOME SIZE]; } base() { delete p; } class derived : public base char *dp; ~derived() { dp = new char[[SOME SIZE]; } derived() { delete dp; } Теперь рассмотрим этот вызов: base *p = new derived; ... delete p; Запомните, что компилятор не знает, что p на самом деле указывает на объект производного класса. Он исходит из того, что p указывает на объявленный тип base. Следовательно, delete p в действительности превращается в: base destructor(p); free(p); Деструктор производного класса никогда не вызывается. Если вы переопределите эти классы, сделав деструктор виртуальным: virtual ~base() { /* ... */ } то компилятор получит доступ к нему при помощи таблицы виртуальных функций, просто как к любой другой виртуальной функции. Так как деструктор теперь виртуальный, то delete p превращается в: ( p-> vtable[DESTRUCTOR SLOT] ) (p); Так как p указывает на объект производного класса, то вы получаете деструктор производного класса, который после выполнения компоненты производного класса вызывает деструктор базового. 140. Функции базового класса, имеющие то же имя, что и функции производного класса, обычно должны быть виртуальными Помните, что открытая (public) функция является обработчиком сообщений. Если базовый класс и производный класс оба имеют обработчики сообщений с одним и тем же именем, то вы скажете, что объект производного класса должен делать что-то отличное от объекта базового класса, чтобы обрабатывать то же самое сообщение. Весь смысл наследования в том, чтобы иметь возможность писать код общего назначения на языке объектов базового класса и обеспечивать работу этого кода даже с объектами производного класса. Следовательно, сообщение должно обрабатываться функцией производного класса, а не базового. Одним распространенным исключением из этого правила является перегрузка операций, где базовый класс может определять некий набор перегруженных операций, а производный класс желает добавить дополнительные перегрузки (в отличие от изменения поведения перегруженных операций базового класса). Хотя перегруженные функции в этих двух классах будут иметь одинаковые имена, у них непременно будут различные сигнатуры, поэтому они не могут быть виртуальными. 141. Не делайте функцию виртуальной, если вы не желаете, чтобы производный класс получил контроль над ней Я читал, что все функции-члены необходимо делать виртуальными просто на всякий случай . Это плохой совет. Ведь вы не желаете, конечно, чтобы производный класс получил контроль надо всеми вашими вспомогательными функциями; иначе вы никогда не будете способны писать надежный код. 142. Защищенные функции обычно должны быть виртуальными Одним из смягчающих факторов в ранее описанной ситуации со сцеплением базового и производного классов является то, что объекту производного класса Си++ едва когда-либо нужно посылать сообщение компоненту своего базового класса. Производный класс наследует назначение (и члены) от базового класса и обычно добавляет к нему назначение (и члены), но производный класс часто не вызывает функции базового класса. (Естественно, производный класс никогда не должен получать доступ к данным базового класса). Единственным иисключением являются виртуальные функции, которые можно рассматривать как средство изменения поведения базового класса. Сообщения часто передаются замещающей функцией производного класса в эквивалентную функцию базового класса. То есть, виртуальное замещение производного класса часто образует цепь с функцией базового класса, которую оно заместило. Например, класс CDialog из MFC реализует диалоговое окно Windows (тип окна для ввода данных). Этот класс располагает виртуальной функцией OnOk(), которая закрывает диалоговое окно, если пользователь щелкнул по кнопке с меткой OK . Вы определяете свое собственное диалоговое окно путем наследования от CDialog и можете создать замещение OnOk() , которое будет выполнять проверку правильности данных перед тем, как позволить закрыть это диалоговое окно. Ваше замещение образует цепь с функцией базового класса для действительного выполнения закрытия: class mydialog : public CDialog ... private: virtual OnOk( void );
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |