Когда читаешь о той или иной реализации механизмов ООП, то весьма забавно видеть, как та или иная конкретная особенность конкретного языка или библиотеки называется «очевидной». Особенно выделяется в этом отношении описание ООП в C++, в котором по факту реализация одна из самых непрозрачных и запутанных.
Так вот, я ничего не буду писать про очевидность, а расскажу про способ облегчить себе жизнь в критических точках жизненного цикла объектов, используя не «очевидные», но весьма полезные особенности реализации ООП в Delphi.
Если конструктор хранит свои данные только в полях объекта, то решение элементарно — достаточно возбудить исключение. RTL Delphi сама его перехватит, вызовет деструктор, высвободит занятую память и возбудит исключение повторно.
Соответственно, если часть данных для конструирования хранится в глобальных переменных, то достаточно использовать обычный блок try..except с повторным возбуждением исключения.
Отсюда выводятся два требования к деструкторам: не провоцировать исключения, что означает не пытаться заниматься чем-либо, кроме освобождения ресурсов (например, сохранением настроек) и обязательно поддерживать…
В Delphi это не представляет никаких трудностей, так как любой объект еще до передачи управления конструктору инициализируется нулями. Соответственно, в деструкторе достаточно корректно обрабатывать нулевые значения, в чем сильно помогает процедура FreeAndNil, которая освобождает объект, только если ссылка на него уже не nil.
В Delphi он не имеет значения. Можно вызывать конструкторы предка, другие конструкторы того же класса точно так же, как и обычные методы или не вызывать ничего — так что если надо использовать инициализацию предка, то забывать ее вызвать не рекомендуется, зато и проблем сделать что-то до или вместо вызова конструктора предка нет никаких.
Так как в Delphi объект сразу инициализируется конечной ссылкой на VMT, то никакой разницы между вызовами виртуальных методов из конструктора от прочих вариантов нет.
В Delphi конструкторы могут быть виртуальными. Смысл такой особенности — возможность создания объектов с неизвестным на этапе компиляции классом без необходимости реализации фабрики. С этой целью используется переменные со ссылкой на класс (а не объект!), для которых и можно вызвать виртуальный конструктор, получив экземпляр соответствующего класса или его потомка, в зависимости от значения ссылки.
Если объект реализует тот или иной явно заданный интерфейс, то его можно привести к ссылке на этот интерфейс, используя присваивание или операцию as. В этом случае Delphi будет сама генерировать вызовы методов IUnknown, что позволяет не запрашивать интерфейсы и не удалять объекты явно.
С одной стороны, все вышеперечисленное должен знать каждый разработчик; с другой — я встречал не так уж мало программистов со стажем, изобретающих велосипеды только от незнания особенностей реализации своего рабочего инструмента. Надеюсь, что эта статья немного поможет. Из полезных особенностей реализации ООП в Delphi осталась нерассмотренной поддержка делегирования реализации интерфейсов, но это тема для отдельной статьи.
Так вот, я ничего не буду писать про очевидность, а расскажу про способ облегчить себе жизнь в критических точках жизненного цикла объектов, используя не «очевидные», но весьма полезные особенности реализации ООП в Delphi.
Обработка невозможности завершить создание объекта в конструкторе
Если конструктор хранит свои данные только в полях объекта, то решение элементарно — достаточно возбудить исключение. RTL Delphi сама его перехватит, вызовет деструктор, высвободит занятую память и возбудит исключение повторно.
Соответственно, если часть данных для конструирования хранится в глобальных переменных, то достаточно использовать обычный блок try..except с повторным возбуждением исключения.
Отсюда выводятся два требования к деструкторам: не провоцировать исключения, что означает не пытаться заниматься чем-либо, кроме освобождения ресурсов (например, сохранением настроек) и обязательно поддерживать…
Удаление частично инициализированного объекта
В Delphi это не представляет никаких трудностей, так как любой объект еще до передачи управления конструктору инициализируется нулями. Соответственно, в деструкторе достаточно корректно обрабатывать нулевые значения, в чем сильно помогает процедура FreeAndNil, которая освобождает объект, только если ссылка на него уже не nil.
Порядок вызова конструкторов
В Delphi он не имеет значения. Можно вызывать конструкторы предка, другие конструкторы того же класса точно так же, как и обычные методы или не вызывать ничего — так что если надо использовать инициализацию предка, то забывать ее вызвать не рекомендуется, зато и проблем сделать что-то до или вместо вызова конструктора предка нет никаких.
Вызов виртуальных методов в конструкторе
Так как в Delphi объект сразу инициализируется конечной ссылкой на VMT, то никакой разницы между вызовами виртуальных методов из конструктора от прочих вариантов нет.
Виртуальные конструкторы и ссылки на класс
В Delphi конструкторы могут быть виртуальными. Смысл такой особенности — возможность создания объектов с неизвестным на этапе компиляции классом без необходимости реализации фабрики. С этой целью используется переменные со ссылкой на класс (а не объект!), для которых и можно вызвать виртуальный конструктор, получив экземпляр соответствующего класса или его потомка, в зависимости от значения ссылки.
Автоматическое управление временем жизни объекта
Если объект реализует тот или иной явно заданный интерфейс, то его можно привести к ссылке на этот интерфейс, используя присваивание или операцию as. В этом случае Delphi будет сама генерировать вызовы методов IUnknown, что позволяет не запрашивать интерфейсы и не удалять объекты явно.
Итоги
С одной стороны, все вышеперечисленное должен знать каждый разработчик; с другой — я встречал не так уж мало программистов со стажем, изобретающих велосипеды только от незнания особенностей реализации своего рабочего инструмента. Надеюсь, что эта статья немного поможет. Из полезных особенностей реализации ООП в Delphi осталась нерассмотренной поддержка делегирования реализации интерфейсов, но это тема для отдельной статьи.