Destructor — Ключевое слово Delphi


Содержание

Почему деструктор в Delphi назван?

Деструкторы в Delphi обычно называются «Destroy», однако, насколько я понимаю, вы также можете

  • деструкторы имен по-разному
  • имеют несколько деструкторов

Есть ли причина, почему это было реализовано таким образом? Каковы возможные варианты использования для разных названных/множественных деструкторов?

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

Кроме того, поскольку язык Object Pascal не имеет этих магических операций new/delete, должен быть только некоторый идентификатор для вызова объекта.

Я бы предпочел посмотреть на это ретроспективно.

Объекты стиля «Turbo Pascal with Objects» имеют и то, и другое — вы вызываете «магическую» процедуру Dispose , но явно указываете деструктор для вызова, поскольку сам язык не знал, что выбрать. Аналогично «магическая» процедура New должна была быть снабжена вручную выбранным конструктором.

Однако это нарушает принцип DRY: компилятор знает, что мы вызываем d-tor или c-tor, но все же мы должны дополнительно называть эти функции «New» и «Dispose». В теории, возможно, было предусмотрено разделить выделение памяти и подачу информации и в любом случае объединить их. Но я не думаю, что эта функция действительно использовалась как угодно.

Интересно, что тот же дизайн используется в Apple Objective C. Вы выделяете память для объекта, а после этого вы вызываете конструктор для этого нового экземпляра:

Когда эта модель была оптимизирована для Delphi, было принято несколько решений, чтобы сделать вещи более упрощенными (и унифицированными). Стратегия распределения памяти [de] была перенесена на уровень класса, а не на сайт вызова. Это сделало избыточность обоих вызовов «Новый» и названный конструктор очень контрастным. Нужно было отказаться.

С++/С#/Java выбрана для сохранения на нем специальных ключевых слов на языке, используя перегруженные функции для предоставления различных c-торов. Возможно, это соответствует американскому стилю компьютерных языков.

Однако у Паскаля в его основе есть две идеи: многословие и небольшая лексика. Возможно, их можно отследить на других европейских школьных языках, таких как Scala. Если возможно, ключевые слова должны быть удалены с самого языка и перенесены на внешние модули — библиотеки, которые вы можете добавить или удалить из проекта. И перегруженные функции были введены намного позже на язык, и раннее предпочтение состояло в том, чтобы, конечно, иметь два имени, названных по-разному (самодокументированные).

Эти обе идеи, вероятно, заставили Delphi удалить «магические» процедуры и вывести создание/уничтожение объекта на сайте-вызове только с помощью имен используемых функций. Если вы вызываете MyVar.Destroy , тогда компилятор просматривает объявление .Destroy и знает, что мы удаляем объект. Точно так же известно, что TMyType.CreateXXX(YYY,ZZZ) — это объектная инстанция из-за того, что был объявлен CreateXXX .

Чтобы сделать c-tor и d-tor без имени, как в С++, Delphi пришлось бы ввести еще два ключевых слова на уровень языка, например, С++ New и delete . И в этом нет явного преимущества. По крайней мере, лично мне лучше нравится Delphi.

PS. Я должен был добавить туда одно предположение: мы говорим о реальных языках С++ и Delphi, как это было в 1995 году. Они отображали только ручное управление памятью для объектов, выделенных кучей, без сбора мусора и автоматического пересчета. Вы не могли инициировать уничтожение объекта, назначив переменную указателем nil/NULL.

Destructor — Ключевое слово Delphi

Конструкторы — это специальные методы, создающие и инициализирующие объект класса. Объект создается выделением для него памяти в динамически распределяемой области памяти heap. Объявление конструктора выглядит так же, как объявление процедуры, но предваряется ключевым словом constructor. В качестве имени конструктора обычно задают имя Create.

В реализации конструктора обычно первым идет вызов наследуемого конструктора с помощью ключевого слова inherited. В результате инициализируются все наследуемые поля. При этом порядковым типам в качестве начального значения задается 0, указателям — nil, строки задаются пустыми. После вызова наследуемого конструктора в процедуре инициализируются новые поля, введенные в данном классе.

Не обязательно при создании класса объявлять его конструктор. Если он не объявлен, то автоматически в момент создания объекта вызовется конструктор родительского класса. Мы это видели в примере класса TPerson и его тестового приложения, созданных во второй части. Там мы обошлись без конструктора, и все работало нормально. Но давайте несколько расширим возможности нашего класса, и посмотрим, сможем ли мы обойтись без конструктора. Добавим в класс открытые поля AgeMin, AgeMax, обозначающие минимальную и максимальную границы допустимого возраста. Подобные границы полезны, если приложение использует наш класс для регистрации претендентов на какую-то должность при приеме на работу, или для регистрации абитуриентов при приеме в учебное заведение. Пусть при задании года рождения класс автоматически проверяет, удовлетворяет ли регистрируемая личность поставленным возрастным ограничениям. Для реализации этой идеи в объявлении класса надо произвести следующие изменения:

В оператор uses вводится ссылка на модуль DateUtils, В этом модуле объявлены функции, которые мы будем использовать при записи года рождения. В защищенный раздел класса protected вводится объявление процедуры записи года рождения. В открытый раздел класса вводятся переменные AgeMin и AgeMax. И изменяется объявление свойства Year: теперь в него вводится ссылка на процедуру записи SetYear.
Реализация процедуры записи SetYear может быть следующей:

В переменную NowYear заносится текущий год. Делается это так. Вызывается функция Date, которая возвращает текущую дату. Затем к этому результату применяется функция YearOf, которая выделяет из даты год. Функция YearOf объявлена в модуле DateUtils. Именно из-за нее этот модуль подключается предложением uses.

Остальное просто: проверяется, укладывается ли заданный год рождения Value в заданный диапазон, и если не укладывается, пользователю выдается соответствующее сообщение.

Возможно, вы уже заметили, почему все это пока не будет нормально работать. Если нет, введите описанные изменения в ваш класс и выполните тестовое приложение. Вы увидите, что можете занести в объект класса только того, кто родился в текущем году. А такой гражданин, пожалуй, слишком молод для приема на работу или учебу. Объясняется это просто: целые поля, такие как AgeMin и AgeMax, инициализируются нулевыми значениями. А нам нужны какие-то другие, более разумные возрастные рамки. Конечно, пользователь класса легко может этот недостаток устранить. Достаточно ввести, например, в обработчик события формы OnCreate после оператора создания объекта класса операторы вида:

Pers.AgeMax := 45;
Pers.AgeMin := 16;

Тем самым пользователь задает возрастные рамки для своего приложения. Ведь открытые поля AgeMin и AgeMax мы для того и вводили. Но хотелось бы, чтобы в случаях, если пользователю не требуется какой-то специфический возрастной диапазон, класс позволял бы работать с любыми реальными годами рождения. Но именно реальными, чтобы предотвратить случайную ошибку пользователя, когда он задаст, например, год рождения 3 или 3003.

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

А в раздел implementation добавьте реализацию конструктора:

Первый оператор вызывает с помощью ключевого слова inherited наследуемый конструктор родительского класса. А затем задаются начальные значения различных полей. Кроме задания максимального возраста 150 лет, тут исправляются еще некоторые недостатки нашего класса. Задается значение пола равное нулевому символу. По такому значению в дальнейшем можно проверять, был ли указан пол личности. И задаются строки «неизвестный» в качестве начального значения строковых нолей. Так что теперь, если какие-то данные о личности неизвестны, вместо них будет выдаваться этот текст, а не пустая строка. Думаю, что пользователю это будет удобно.

Теперь рассмотрим деструкторы. Это специальные методы, уничтожающие объект и освобождающие занимаемую им память. Деструктор автоматически вызывается при выполнении метода Free объекта класса. Если в вашем классе деструктор не объявлен, вызывается деструктор родительского класса.

В большинстве случаев объявлять деструктор в классе не требуется. И мы до сих пор прекрасно без него обходились. Деструктор нужен в тех случаях, когда в конструкторе или в каком-то методе класса создается объект, динамически размещаемый в памяти. Тогда нужен деструктор, чтобы уничтожить этот объект и освободить занимаемую им память. Аналогично деструктор требуется, если объект создает и хранит информацию в каких-то временных файлах. Тогда в деструкторе надо уничтожить эти файлы, чтобы они не оставались на диске.

Давайте введем в наш класс TPerson еще одно добавление, которое расширит его возможности. Создадим возможность хранить форматированный текст в формате .rtf, который может поступать, как вы знаете, из окна RichEdit. Но форматированный текст можно хранить или в объекте класса TRichEdit, или в файле, в который он записан методом SaveToFile свойства Lines окна RichEdit. Создать в нашем классе внутренний объект класса TRichEdit мы не можем. Так что остается вариант хранения во временном файле.
Добавьте в класс TPerson два поля:

FDoc: ^TRichEdit;
FileTMP: string;

Поле FDoc будет служить указателем на внешний компонент класса TRichEdit, из которого будет загружаться форматированный текст в файл, и в который будет грузиться текст из файла. А в переменной FileTmp будем хранить имя временного файла. Чтобы компилятор принял объявление поля FDoc, он должен понять идентификатор TRichEdit. Этот класс объявлен в модуле ComCtrls. Так что добавьте ссылку на этот модуль в предложение uses.
Введите в открытый раздел класса объявления двух процедур:

procedure SetDoc(var Value: TRichEdit); procedure GetDoc(var Value: TRichEdit);

Первая из них будет запоминать форматированный текст из окна RichEdit, переданного в нее как указатель на объект (вспомните смысл ключевого слова var). А вторая процедура будет заносить в окно, указанное аналогичным образом ее параметром, текст из файла. Реализация этих функций может иметь вид:

В процедуре SetDoc сначала проверяется, задавалось ли уже имя временного файла. Если нет, то это имя формируется из строки FName (фамилия, имя, отчество) и расширения .tmp. Затем переменной FDoc задается адрес компонента TRichEdit. А дальнейшее понятно: форматированный текст записывается в файл с именем, хранящимся в переменной FileTMP.

Добавьте в свое тестовое приложение окно RichEdit. Можете добавить также компонент FontDialog и обеспечить возможность форматирования текста в окне RichEdit. В обработчик щелчка на кнопке Запись вставьте оператор:

А в обработчик щелчка на кнопке Чтение вставьте оператор:

Выполнив тестовое приложение, можете убедиться, что ваш класс стал намного мощнее и может теперь хранить форматированный текст. Но он пока не совсем правильно оформлен. Если в приложении выполнялся вызов процедуры SetDoc, то был создан временный файл. И когда приложение завершается, этот файл остается на диске. Вам нужно написать деструктор, который удалял бы этот файл.

Объявление деструктора выглядит так же, как объявление процедуры, но предваряется ключевым словом destructor. В качестве имени деструктора обычно задают имя Destroy. Реализация деструктора, как правило, завершается вызовом наследуемого деструктора с помощью ключевого слова inherited, чтобы освободить память, отведенную для наследуемых полей.

В нашем случае в открытый раздел класса следует ввести объявление деструктора:


destructor Destroy; override;

Смысл ключевого слова override вы узнаете позднее. Пока просто напишите его, не задумываясь о его назначении. Реализация деструктора имеет вид:

Если файл создавался, то он уничтожается. Введение такого деструктора никак не отразится на внешнем поведении вашего тестового приложения. Но теперь вы не оставляете на диске временный и уже ненужный файл.

Следующий урок будет посвящён методам, наследованиям классов, операциям с классами.

Удачи!
Встретимся в следующем уроке!

Источник: www.thedelphi.ru
Автор: Савельев Александр
Опубликовано: 7 Июля 2013
Просмотров: 11418

Зарегистрируйтесь или авторизуйтесь, чтобы добавлять комментарии.

Деструктор и Free

30.06.2009, 13:27

Закрытие формы кроме метода Free
Здравствуйте! У меня вот возник такой вопрос — я из своего приложения запускаю чужое приложение и.

Lock-free синхронизация: как организуется нотификация между потоками?
Добрый день! Хотелось бы услышать о реальном опыте, потому что теоретических советов в гугле.

Не работает деструктор
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls.

Timer в Delphi, деструктор
Добрый день. Есть процедуры редактирования фигур, в которых применяется таймер. Один из методов.

Должен ли я использовать «виртуальное» ключевое слово для деструктора базового класса, который на самом деле является интерфейсом?

У меня есть абстрактный базовый класс и производный класс:

Мне действительно нужно сделать виртуальный деструктор базового класса, как в C ++, или все будет в порядке, если он не виртуальный?
Кстати, я использовал «переопределить» право или мне нужно «перегрузка»?

2 ответа

Переопределение верно — вы переопределяете виртуальный метод.

Если вы действительно хотите, чтобы деструктор TInterfaceMethod генерировал EAbstractError, вы должны пометить его как ‘override; Аннотация;’. (Я удивлен, что это работает, но я проверил с D2007 и это делает.) Но почему вы хотите это сделать?

Кстати, нет необходимости использовать отдельный блок ‘type’ для каждого объявления. Вы можете отформатировать код так:

Кроме того, вы, скорее всего, должны использовать интерфейсы вместо абстрактного базового класса. И вы должны пометить класс TInterfaceMethod «абстрактный», как указано выше. Теоретически это помешает вам создать объект TInterfaceMethod напрямую. (На практике мой D2007 позволяет это — странно.)

Просто чтобы придираться ;-).

Виртуальный деструктор уничтожения уже существует (и используется Free). Поэтому, если вы объявите такой класс, у вас возникнут некоторые проблемы.

Илон Маск рекомендует:  Что такое код hw_insertdocument

Но суммируем некоторые директивы метода:

  • virtual используется для определения виртуального метода (использует привязку во время выполнения).
  • override используется для определения метода, который переопределяет виртуальный метод.
  • абстрактный используется с виртуальным (или переопределить!) для определения метода без реализации. Он должен быть переопределен в подклассе, иначе он не может быть вызван.
  • final используется с virtual или override, чтобы определить метод, который нельзя переопределить.
  • Повторное введение используется, если вы хотите повторно ввести метод, который уже находится в подклассе, без его переопределения. Он подавляет предупреждение, которое вы получаете. И заботится о нежелательном способе сокрытия.

Заметки о Pascal, Delphi и Lazarus

Следует ожидать переводов разделов справочной системы Delphi, компиляций из учебников, переводы статей, «путевые заметки» и прочие интересности. Блог прежде всего ориентирован на студентов, но опытных людей я тоже буду рад видеть;-)

среда, 2 мая 2012 г.

Методы

Метод – это процедура или функция, связанная с классом. При вызове метода определяется объект (или, если это метод класса, то класс) с которым должен работать метод. Например, SomeObject.Free вызывает метод Free объекта SomeObject.

О методах

Внутри объявления класса методы представлены заголовками процедур или функций, которые работают как упреждающие объявления. Где-то после объявления класса (но в этом же модуле) каждый метод должен быть реализован добавлением определяющего объявления. Например, предположим, что объявление TMyClass включает метод с именем DoSomething:

Определяющее объявление для DoSomething должно быть включено ниже в этом же модуле:

Класс может быть объявлен в модуле как в секции interface, так и в секции implementation, но определяющие объявления методов должны быть расположены в секции implementation.

В заголовке определяющего объявления имя метода обязательно специфицируется именем класса, к которому он принадлежит. Заголовок может повторять список параметров в из упреждающего объявления и, в таком случае, порядок, тип и имена параметров должны в точности совпадать с упреждающим объявлением. Если метод является функцией, тип возвращаемого ею результата также должен совпадать с упреждающим объявлением.

Объявления методов могут включать специальные директивы, которые не используются при объявлении прочих процедур и функций. Директивы должны указываться только в объявлении класса, но не в определяющем объявлении, и могут следовать только в следующем порядке: reintroduce; overload; binding; calling convention; abstract; warning, где:

  • binding может принимать значения: virtual, dynamic или override;
  • calling convention может принимать значения: register, pascal, cdecl, stdcall или safecall;
  • warning может принимать значения: platform, deprecated или library.

Ключевое слово Inherited

Ключевое слово inherited играет особую роль при реализации полиморфизма в поведении методов. Оно может встречаться в объявлении методов, причем иногда с указанимем идентификатора после него, а иногда – без.

Если за ключевым словом inherited следует имя компонента, оно обозначает обычный вызов метода, обращение к свойству или полю, за исключением того, что поиск компонента, к которому идет обращение, начинается с непосредственного предка класса, к которому относится компонент. Например, если в определяющем объявлении метода присутствует:

происходит вызов наследуемого конструктора Create.


Когда за ключевым словом inherited не следует идентификатора, обращение идет к методу с таким же именем, как вызывающий метод, или (в том случае, когда вызывающий метод является обработчиком сообщений) к наследуемому обработчику для этого же сообщения. В этом случае вызов наследуемого метода не принимает явных параметров, но при этом выполняется неявная передача параметров из вызывающего метода. Например, вызов:

встречается часто в реализации конструкторов. Вызывает наследуемый конструктор с таким же списком параметров (передаваемых наследуемому конструктору).

Идентификатор Self

При реализации метода идентификатор Self обращается к объекту, из которого происходит вызов метода. Для примера приведем реализацию метода Add класса TCollection, объявленного в модуле Classes:

Метод Add вызывает метод Create класса, на который ссылается поле FitemClass (который всегда является наследником TCollectionItem). TCollectionItem.Create принимает единственный параметр типа TСollection. Таким образом, метод Add передает экземпляр объекта типа TCollection, вызывающего его. Это аналогично коду:

Self полезен по самым различным причинам. Например, если идентификатор компонента объявлен в классе, он может быть объявлен повторно в блоке метода, относящегося к этому классу. В этом случае, вы можете получить доступ к идентификатору компонента, используя вызов Self.Identifier.

Связывание методов

Связывание методов может быть статическим (static) (по умолчанию), виртуальным (virtual) или динамическим (dynamic). Виртуальные и динамические методы могут перекрываться и они могут быть абстрактными. Это подразделение методов начинает иметь значение, когда переменная одного типа класса хранит значение типа класса-потомка. Оно определяет, какая реализация метода будет активирована при запуске метода.

Статические методы

Методы по умолчанию являются статическими. При вызове статического метода, объявленный при компиляции тип класса или объектная переменная, использованные при вызове метода определяют, какая из реализаций метода активируется. В следующем примере метод Draw является статическим:

При таком объявлении приведенный ниже код иллюстрирует вызов статического метода. Во втором вызове Figure.Draw, переменная Figure ссылается на класс TRectangle, но вызов метода активирует реализацию метода Draw в классе TFigure, поскольку объявленный тип переменной Figure – это TFigure:

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

Для того, чтобы сделать метод виртуальным или динамическим необходимо включить в его объявление соответствующую директиву (virtual или dynamic). Виртуальные и динамические методы, в отличии от статических могут быть перекрыты в классе-предке. При вызове перекрытого метода, решение о том, какую реализацию метода активировать, принимает действительный (определенный в процессе работы программы) тип класса или объекта, использванный при вызове метода.

Чтобы перекрыть метод его следует объявить повторно с указанием директивы override. Перекрывающее объявление должно совпадать с объявлением в классе-предке по порядку и типу параметров, а так же типу возвращаемого результата (для функций). В следующем примере метод Draw, объявленный в классе TFigure перекрывается в двух классах предках:

При таком объявлении нижеприведенный код показывает результат вызова виртуального метода при помощи переменной, чей тип меняется в процессе выполнения программы:

Только виртуальные и динамические методы могут быть перекрыты. Тем не менее, любые методы могут быть перегружены. Компилятор Delphi так же поддерживает концепцию финального виртуального метода. Если при объявлении виртуального метода было использовано ключевое слово final, классы-предки не смогут перекрывать его. Применение ключевого слова final может быть хорошим инженерным решением, которое может помочь документировать планируемое применение класса. Кроме того, оно помогает компилятору оптимизировать код.

Виртуальный или динамический?

В Delphi для Win32, виртуальные и динамические методы семантически одинаковы. Тем не менее, они отличаются в реализации обработки вызовов методов при выполнении программы. Виртуальные методы оптимизированы с точки зрения скорости, а динамические — снижают размер результирующего кода.

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

Замечание: Динамические методы следует использовать только в тех случаях, когда выгода от их применения очевидна. Во всех обычных ситуациях следует использовать виртуальные методы.

Скрывать или перекрывать?

Если объявление метода определяет такой же идентификатор и набор параметров, какой уже присутствует в объявлении метода в классе-предке, но при этом не использует директиву override, новое объявление просто скрывает наследуемый метод, не перекрывая его. Оба метода будут существовать в классе-предке, в котором имя метода связано статически. Например:

Директива Reintroduce

Директива reintroduce подавляет предупреждения компилятора о сокрытии объявленных ранее виртуальных методов. Например:

Директиву reintroduce следует использовать, когда вы хотите скрыть наследуемый виртуальный метод, заменив его новым методом.

Абстрактные методы

Абстрактные методы – это виртуальные или динамические методы, не имеющие реализации в том классе, где они объявлены. Они должны быть реализованы в классе-потомке. Абстрактные методы должны объявляться с использованием директивы abstract, следующей за директивой virtual или dynamic. Например:

Вы можете вызывать абстрактный метод только в классе или экземпляре, перекрывающем этот метод.

Методы класса

Большая часть методов называются методами экземпляров, поскольку они работают с отдельными экземплярами объекта. Метод класса – это метод (отличный от конструктора), который оперирует классами, но не объектами. Существует два типа методов класса: обычные и статические.

Обычные методы класса

Объявление метода класса должно начинаться с зарезервированного слова class. Например:

Определяющее объявление метода класса также начинается с зарезервированного слова class:

В определяющем объявлении метода класса идентификатор Self представляет класс, в котором вызывается метод (может быть классом-потомком, по отношению к классу, в котором объявлен метод). Если методы вызывается в классе С, Self имеет тип class of C.

Таким образом, вы не сможете использовать Self для доступа к полям, свойствам или обычным методам экземпляра. Вы можете использовать Self для вызова конструкторов, прочих методов класса или для доступа к свойствам или полям класса. Метод класса может быть вызван при помощи ссылки на класс или объект. При вызове через ссылку на объект, класс объекта принимает значение Self.

Статические методы класса

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

Для объявления статического метода класса следует добавить ключевое слово класса к объявлению:

Как и обычные методы класса, статические методы могут вызываться при помощи типа класса (без ссылки на экземпляр класса):

Перегрузка методов

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

Если вы перегружаете виртуальный метод, — пользуйтесь директивой reintroduce:

Внутри класса вы не можете установить видимость перегружаемых методов с одинаковыми именами в published. Обработка информации о типах в режиме выполнения программы требует уникального имени для компонентов с такой спецификацией видимости.


Методы, служащие для спецификации доступа к значению свойств (read или write) не могут быть перегружены. Реализация перегружаемых методов должна повторять список параметров, содержащихся в объявлении класса.

Конструкторы

Конструктор – это особый метод, который создает и инициализирует объекты. Объявление конструктора выглядит как объявление обычной процедуры, но начинается с ключевого слова constructor. Примеры:

Конструкторы должны использовать конвенцию вызова register. При том, что в объявлении конструктора не указывается возвращаемого значения, конструктор возвращает ссылку на создаваемый или вызывающий его объект.

Класс, может иметь более, чем один конструктор, но большая часть классов имеет только один. Обычно для конструкторов используется имя Create.

Для создания объекта следует вызывать конструктор, специфицируя название типа класса. Например:

Эта инструкция выделяет память для нового объекта, устанавливает значения для всех целочисленных полей в 0, присваивает значение nil для всех полей указательного типа и устанавливает пустое значение для всех строковых полей. Прочие операции, указанные в реализации конструктора, выполняются позже. Обычно объекты инициализируются значениями, которые передаются как параметры в конструктор. Наконец, конструктор возвращает ссылку на только что созданный и инициализированный объект. Тип возвращаемого значения совпадает с типом, указанным при вызове конструктора.

Если при выполнении конструктора, вызванного инструкцией с указанием ссылки на класс, возникает исключение деструктор Destroy вызывается автоматически для разрушения незаконченного объекта.

Когда конструктор вызывается инструкцией с указанием ссылки на объект, он не создает объект, а работает с вызывающим его объектом, выполняя только инструкции, указанные в его реализации, а затем возвращает ссылку на объект. Конструктор обычно вызывается инструкцией со ссылкой на объект в сочетании с зарезервированным словом inherited для выполнения наследуемого конструктора.

Далее приведен пример класса и его конструктора:

Первой инструкцией в конструкторе обычно бывает вызов наследуемого конструктора для инициализации унаследованных полей. Затем конструктор инициализирует поля, объявленные в классе-потомке. Поскольку конструктор всегда очищает память, которую он выделяет для создания объекта, все поля порядковых типов принимают значение 0, указатели и переменные типа класс – nil, строковые типы инициализируются пустым значением, а вариантные типы – значением Unassigned. Таким образом нет необходимости в теле конструктора инициализировать поля значениями, отличными от ноля или пустых значений.

При вызове через идентификатор типа класса конструктор, объявленный как virtual, ведет себя так же как обычный статический конструктор. Тем не менее, при вызове через ссылку на класс виртуальные конструкторы допускают полиморфное создание объектов – то есть создание объектов, тип которых неизвестен на этапе компиляции.

Деструкторы

Деструктор – это особый метод, который при вызове разрушает объект и освобождает занимаемую им память. Объявление деструктора выглядит как объявление обычной процедуры, но начинается с ключевого слова destructor. Например:

Деструкторы на платформе Win32 используют по умолчанию конвенцию вызова register. Хоть класс и может иметь несколько деструкторов, рекомендуется перекрывать наследуемый метод Destroy, без объявления прочих деструкторов.

Для вызова деструктора вы должны специфицировать экземпляр объекта:

При вызове деструктора вначале выполняются инструкции, указанные в его теле. Обычно это уничтожение встроенных объектов и высвобождение ресурсов, выделенных для разрушаемого объекта. После чего память, выделенная для объекта освобождается.

Далее приведен пример реализации деструктора:

Последней инструкцией в теле деструктора обычно бывает вызов наследуемого деструктора, которые уничтожает наследуемые поля. При возникновении исключительной ситуации в процессе создании объекта, для разрушения незаконченного объекта автоматически вызывается деструктор Destroy. Это обозначает, что деструктор Destroy должен быть подготовлен к разрушению частично созданных объектов. Поскольку конструктор устанавливает нулевые и пустые значения полей перед выполнением каких-либо других действий, поля типа класс и указатели в частично созданном объекте всегда имеют значение nil. Прежде чем работать с такими полями деструктор должен проверять их на наличие этого значения. Вызов метода Free (объявлен в TObject) – это более подходящий способ для разрушения объекта с предварительной проверкой значений его полей.

Конструкторы классов

Конструктор класса – это особый метод класса, который недосупен разработчикам. Вызовы конструкторов автоматически вставляются компилятором в код секции инициализации модуля, в котором объявлен класс. Обычно конструкторы классов применяются для инициализации статических полей класса или для выполнения особого типа инициализации, требуемой для того, чтобы класс или какой-либо его экземпляр функционировал корректно. Даже если такой же результат может быть достигнут размещением кода инициализации класса в секции initialization, конструкторы класса в большей степени помогают компилятору решить, какой именно класс должен быть включен в результирующий двоичный файл, а какой – удален из него.

Илон Маск рекомендует:  Элементы нечеткой логикиh3ce

Следующий пример показывает обычную инициализацию полей класса:

Этот способ плох тем, что приложение включает модуль, объявляющий TBox, даже в том случае, если этот модуль на самом деле не используется им. В приведенном примере класс TBox включается в результирующий бинарный файл, поскольку он упоминается в секции инициализации. Чтобы исправить ситуацию, рассмотрим применение конструкторов класса:

В этом случае, компилятор проверяет, используется ли где-либо в приложении класс TBox, и, если это так, вызов конструктора класса автоматически добавляется в секцию инициализации модуля.

Замечание: Несмотря на то, что компилятор уделяет внимание порядку инициализации классов, в некоторых сложных ситуациях он может оказаться случайным. Это происходит, когда конструктор класса зависит от состояния другого класса, который, в свою очередь, зависит от первого.
Замечание: конструктор класса-дженерика или записи может быть выполнен несколько раз. Точное количество запусков конструктора зависит от количества специализированных версий типа-дженерика. Например, конструктор класса для специализированного класса TList&ltString&gt может быть выполнен несколько раз в одном и том же приложении.

Деструкторы классов

Деструкторы классов выполняют финализацию класса. Деструкторы класса обладают тем же преимуществом, что и конструкторы класса. Следующий пример построен на предыдущем (о конструкторах класса) и объявляет финализирующую подпрограмму:

Замечание: деструктор класса-дженерика или записи может быть выполнен несколько раз. Точное количество запусков деструктора зависит от количества специализированных версий типа-дженерика. Например, деструктор класса для специализированного класса TList&ltString&gt может быть выполнен несколько раз в одном и том же приложении.

Методы-обработчики сообщений

Обработчики сообщений реализуют реакцию на динамически передаваемые сообщения. Синтаксис метода-обработчика сообщения поддерживается для всех платформ. Приложения VCL применяют обработчики сообщений для реагирования на сообщения Windows.

Обработчик сообщения объявляется при помощи директивы message, за которой следует целочисленная константа с диапазоном значений от 1 до 49151, представляющая собой идентификатор сообщения. Для обработчиков сообщений в элементах управления VCL эта константа может быть идентификатором одного из сообщений Win32, определенных в модуле Messages. Метод-обработчик сообщения должен быть процедурой с единственным параметром, передаваемым по ссылке.

Обработчик сообщения не должен содержать директивы override для переопределения наследуемого метода. На самом деле он даже не должен повторять имени или типа параметра перекрываемого метода. Только идентификатор сообщения определяет, какое сообщение должен обработать метод и является ли он перекрывающим методом.

Реализация методов-обработчиков сообщений

Реализация обработчика может вызывать наследуемый метод-обработчик. Например:

Инструкция inherited выполняет поиск по иерархии класса и подключает первый найденный метод с соответствующим идентификатором сообщения и передает ему запись сообщения. Если классы-предки не реализуют метод для обработки сообщения с данным идентификатором, директива inherited вызывает метод DefaultHandler , объявленный в TObject.

Реализация DefaultHandler в TObject не подразумевает выполнения каких-либо операций. Перекрывая DefaultHandler, класс может реализовать собственную обработку сообщений по умолчанию. Для элементов управления на платформе Win32, метод DefaultHandler вызывает Win32 API DefWindowProc.

Обработка сообщений

Обработчики сообщений редко вызываются явно. Обычно сообщения передаются объекту при помощи метода Dispatch , наследуемого от TObject:

Параметр Message передаваемый методу Dispatch должен быть записью, первым полем которой должно быть поле типа Word, содержащее идентификатор сообщения.

Dispatch выполняет поиск обработчика сообщения в иерархии классов (начиная с класса объекта, в котором был вызван этот метод) и запускает первый найденный обработчик для идентификатора, который был ему передан. Если обработчика для сообщения с данным идентификатором не находится, Dispatch вызывает DefaultHandler.

Почему деструктор в Delphi назван? — destructor

Деструкторы в Delphi обычно называются «Destroy», однако, насколько я понимаю, вы также можете

  • деструкторы имен по-разному
  • имеют несколько деструкторов


Есть ли причина, почему это было реализовано таким образом? Каковы возможные варианты использования для разных названных/множественных деструкторов?

    4 1
  • 31 июл 2020 2020-07-31 12:01:19
  • Askaga

1 ответ

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

Кроме того, поскольку язык Object Pascal не имеет этих магических операций new/delete, должен быть только некоторый идентификатор для вызова объекта.

Я бы предпочел посмотреть на это ретроспективно.

Объекты стиля «Turbo Pascal with Objects» имеют и то, и другое — вы вызываете «магическую» процедуру Dispose , но явно указываете деструктор для вызова, поскольку сам язык не знал, что выбрать. Аналогично «магическая» процедура New должна была быть снабжена вручную выбранным конструктором.

Однако это нарушает принцип DRY: компилятор знает, что мы вызываем d-tor или c-tor, но все же мы должны дополнительно называть эти функции «New» и «Dispose». В теории, возможно, было предусмотрено разделить выделение памяти и подачу информации и в любом случае объединить их. Но я не думаю, что эта функция действительно использовалась как угодно.

Интересно, что тот же дизайн используется в Apple Objective C. Вы выделяете память для объекта, а после этого вы вызываете конструктор для этого нового экземпляра:

Когда эта модель была оптимизирована для Delphi, было принято несколько решений, чтобы сделать вещи более упрощенными (и унифицированными). Стратегия распределения памяти [de] была перенесена на уровень класса, а не на сайт вызова. Это сделало избыточность обоих вызовов «Новый» и названный конструктор очень контрастным. Нужно было отказаться.

С++/С#/Java выбрана для сохранения на нем специальных ключевых слов на языке, используя перегруженные функции для предоставления различных c-торов. Возможно, это соответствует американскому стилю компьютерных языков.

Однако у Паскаля в его основе есть две идеи: многословие и небольшая лексика. Возможно, их можно отследить на других европейских школьных языках, таких как Scala. Если возможно, ключевые слова должны быть удалены с самого языка и перенесены на внешние модули — библиотеки, которые вы можете добавить или удалить из проекта. И перегруженные функции были введены намного позже на язык, и раннее предпочтение состояло в том, чтобы, конечно, иметь два имени, названных по-разному (самодокументированные).

Эти обе идеи, вероятно, заставили Delphi удалить «магические» процедуры и вывести создание/уничтожение объекта на сайте-вызове только с помощью имен используемых функций. Если вы вызываете MyVar.Destroy , тогда компилятор просматривает объявление .Destroy и знает, что мы удаляем объект. Точно так же известно, что TMyType.CreateXXX(YYY,ZZZ) — это объектная инстанция из-за того, что был объявлен CreateXXX .

Чтобы сделать c-tor и d-tor без имени, как в С++, Delphi пришлось бы ввести еще два ключевых слова на уровень языка, например, С++ New и delete . И в этом нет явного преимущества. По крайней мере, лично мне лучше нравится Delphi.

PS. Я должен был добавить туда одно предположение: мы говорим о реальных языках С++ и Delphi, как это было в 1995 году. Они отображали только ручное управление памятью для объектов, выделенных кучей, без сбора мусора и автоматического пересчета. Вы не могли инициировать уничтожение объекта, назначив переменную указателем nil/NULL.

Destructor — Ключевое слово Delphi

Ситуация следующая: поток должен уничтожить сам себя, и (в силу некоторых причин) должен вызвать процедуру, которая присвоит экземпляру потока (т.е. себе самому) nil.

Модуль, использующий поток:

Как ни странно, вышеприведенный код успешно работает и не вызывает никаких ошибок. Однако непонятно как это происходит. Ведь после вызова inherited Destroy уничтожается указатель на процедуру, которая вызывается следующей строчкой кода, и успешно (!) присваивает nil экземпляру потока.

Ваше мнение насчет корректности написания и работы подобного кода?

От: grom555
Дата: 14.04.07 11:32
Оценка:

Здравствуйте, Sartorius78, Вы писали:

S>Ситуация следующая: поток должен уничтожить сам себя, и (в силу некоторых причин) должен вызвать процедуру, которая присвоит экземпляру потока (т.е. себе самому) nil.

S>Как ни странно, вышеприведенный код успешно работает и не вызывает никаких ошибок. Однако непонятно как это происходит. Ведь после вызова inherited Destroy уничтожается указатель на процедуру, которая вызывается следующей строчкой кода, и успешно (!) присваивает nil экземпляру потока.

S>Ваше мнение насчет корректности написания и работы подобного кода?

По-моему не сильно корректно.
Можешь пояснить зачем тебе FreeOnTerminate=true и еще NIL присваивать?

От: Sartorius78
Дата: 14.04.07 11:47
Оценка:

G>Можешь пояснить зачем тебе FreeOnTerminate=true и еще NIL присваивать?

Затем, что Free лишь освобождает память, но не присваивает nil.

От: grom555
Дата: 14.04.07 11:51
Оценка:

Здравствуйте, Sartorius78, Вы писали:

G>>Можешь пояснить зачем тебе FreeOnTerminate=true и еще NIL присваивать?

S>Затем, что Free лишь освобождает память, но не присваивает nil.

Эт понятно
Только вот зачем тебе NIL?
Пусть не будет равен нил и все. Главное, что ты память освободил.

От: Sartorius78
Дата: 14.04.07 12:05
Оценка:

G>Пусть не будет равен нил и все. Главное, что ты память освободил.

А как иначе проверить, существует ли поток?

От: grom555
Дата: 14.04.07 12:15
Оценка:

Здравствуйте, Sartorius78, Вы писали:

G>>Пусть не будет равен нил и все. Главное, что ты память освободил.

S>А как иначе проверить, существует ли поток?

Вот в этом и вопрос. Ты создаешь поток со свойством FreeOnTerminate, а потом хочешь отследить когда он закончился.
Можно все сделать иначе:

Это вставь там, где ты проверяешь работает ли поток и создаешь новый(если нет).
Все. Подходит?


От: Sartorius78
Дата: 14.04.07 13:36
Оценка:

G>begin
G>//.
G> with (TScanThread.CreateIt) do
G> begin
G> FreeOnTerminate := True;
G> Resume;
G> end;
G>//.
G>end;
G>[/pascal]

G>Это вставь там, где ты проверяешь работает ли поток и создаешь новый(если нет).
G>Все. Подходит?

эм. объясни смысл этого кода пожалуйста

От: Danchik
Дата: 14.04.07 18:38
Оценка:

Здравствуйте, Sartorius78, Вы писали:

[Skip]

S>Как ни странно, вышеприведенный код успешно работает и не вызывает никаких ошибок. Однако непонятно как это происходит. Ведь после вызова inherited Destroy уничтожается указатель на процедуру, которая вызывается следующей строчкой кода, и успешно (!) присваивает nil экземпляру потока.

S>Ваше мнение насчет корректности написания и работы подобного кода?

Все нормально, обьект в этот момент еще не освободился

Но лучше напиши так:

Тут не хендлы важны, а указатели

От: Softwarer http://softwarer.ru
Дата: 16.04.07 17:27
Оценка:

Здравствуйте, Sartorius78, Вы писали:

S> Ведь после вызова inherited Destroy уничтожается указатель на процедуру,

Плюньте в лицо тому, кто сказал Вам такую глупость. Я бы уж скорее позаботился о значении Handle, которое передается в эту процедуру — вот оно действительно может пострадать.

S>Ваше мнение насчет корректности написания и работы подобного кода?

Код не слишком удачный, но не поэтому. Ради потакания паранойе в принципе можно завести локальную переменную, в которую копировать «уничтожаемый указатель» до вызова inherited Destroy.

От: Danchik
Дата: 16.04.07 17:36
Оценка:

Здравствуйте, Softwarer, Вы писали:

[Skip]

S>>Ваше мнение насчет корректности написания и работы подобного кода?

S>Код не слишком удачный, но не поэтому. Ради потакания паранойе в принципе можно завести локальную переменную, в которую копировать «уничтожаемый указатель» до вызова inherited Destroy.

Думаю, было бы неплохо, также намектуть, что клас уничтожится только после последнего оператора деструктора. IOW пока мы в деструкторе, Self валиден.

От: Softwarer http://softwarer.ru
Дата: 16.04.07 17:44
Оценка:

Здравствуйте, Danchik, Вы писали:

D>Думаю, было бы неплохо, также намектуть, что клас уничтожится только после последнего оператора деструктора.

Точнее, последнего оператора последнего деструктора.

От: Danchik
Дата: 16.04.07 17:46
Оценка:

Здравствуйте, Softwarer, Вы писали:

S>Здравствуйте, Danchik, Вы писали:

D>>Думаю, было бы неплохо, также намектуть, что клас уничтожится только после последнего оператора деструктора.

S>Точнее, последнего оператора последнего деструктора.

От: Sartorius78
Дата: 16.04.07 17:56
Оценка:

D>>>Думаю, было бы неплохо, также намектуть, что клас уничтожится только после последнего оператора деструктора.

Допустим, но почему тогда вызов процедурной переменной позволяет успешно присвоить nil? Ведь по вашим высказываниям объект в этот момент еще существует.

От: Danchik
Дата: 16.04.07 18:10
Оценка:

Здравствуйте, Sartorius78, Вы писали:

D>>>>Думаю, было бы неплохо, также намектуть, что клас уничтожится только после последнего оператора деструктора.

S>Допустим, но почему тогда вызов процедурной переменной позволяет успешно присвоить nil? Ведь по вашим высказываниям объект в этот момент еще существует.

Что то я недопонимаю в ваших рассуждениях.
Процедурная переменная еще валидна. Соответственно вызов нормальный. Присвоить в глобальную переменную nil, религия не мешает
Что тут не ясно? nil, в вашем случае, присваивается не экземпляру потока, а к глобальной переменной, которая содержит указатель на экземпляр.

От: Softwarer http://softwarer.ru
Дата: 16.04.07 19:05
Оценка:

Здравствуйте, Sartorius78, Вы писали:

S>Допустим, но почему тогда вызов процедурной переменной позволяет успешно присвоить nil? Ведь по вашим высказываниям объект в этот момент еще существует.

Как Вам сказать. Вам стоит просто разобраться в том, как реализованы механизмы ООП в дельфе. По Вашему изложению создается картина некоторой полумистической каши, совершенно не соответствующая действительности.

От: Аноним
Дата: 18.04.07 05:17
Оценка:
От: Sartorius78
Дата: 18.04.07 05:21
Оценка:

Здравствуйте, Danchik, Вы писали:

D>Что тут не ясно? nil, в вашем случае, присваивается не экземпляру потока, а к глобальной переменной, которая содержит указатель на экземпляр.

Мне неясно, почему попытка присвоения nil в теле деструктора приводит к ошибке. В чем разница?

От: Аноним
Дата: 18.04.07 07:56
Оценка:

Здравствуйте, Sartorius78, Вы писали:

G>>begin
G>>//.
G>> with (TScanThread.CreateIt) do
G>> begin
G>> FreeOnTerminate := True;
G>> Resume;
G>> end;
G>>//.
G>>end;
G>>[/pascal]

G>>Это вставь там, где ты проверяешь работает ли поток и создаешь новый(если нет).
G>>Все. Подходит?

S>эм. объясни смысл этого кода пожалуйста

Смысл вот:
1. Создаешь экземпляр класса TScanThread и работаешь с указателем на него.
2. Меняешь свойство FreeOnTerminate так, чтоб объект сам убился после окончания метода Execute.
3. Запускаешь поток и забываешь про него. Он сам уничтожится после завершения метода Execute.

Илон Маск рекомендует:  Урок 20. Работа с cURL в PHP
От: wallaby
Дата: 18.04.07 08:55
Оценка:

Здравствуйте, Softwarer, Вы писали:

S>> Ведь после вызова inherited Destroy уничтожается указатель на процедуру,

S>Плюньте в лицо тому, кто сказал Вам такую глупость.

Это не такая уж и глупость. В старой объектной модели (Turbo Pascal) использовались настоящие конструкторы и деструкторы, и код

являлся ошибочным, поскольку после inherited Destroy объект уже уничтожен, и ссылка на его поле (FAfterDestroy) не валидна. По аналогичным причинам в конструкторе нельзя было присваивать значения полям объекта до вызова унаследованного конструктора. В новой объектной модели (Delphi) IMHO Destroy — это просто метод, вызываемый внутри настоящего деструктора, скрытого от программиста, поэтому приведённый код корректен.

От: Danchik
Дата: 18.04.07 10:10
Оценка:


Здравствуйте, Sartorius78, Вы писали:

S>Здравствуйте, Danchik, Вы писали:

D>>Что тут не ясно? nil, в вашем случае, присваивается не экземпляру потока, а к глобальной переменной, которая содержит указатель на экземпляр.

S>Мне неясно, почему попытка присвоения nil в теле деструктора приводит к ошибке. В чем разница?

Какое именно присвоение? Куда вы присваиваете?

От: Softwarer http://softwarer.ru
Дата: 19.04.07 20:19
Оценка:

Здравствуйте, wallaby, Вы писали:

W>Это не такая уж и глупость. В старой объектной модели (Turbo Pascal) использовались настоящие конструкторы и деструкторы, и код являлся ошибочным

Ээ. ну Вы уже знаете мою рекомендацию для этого случая

Рекомендация номер два — попробуйте выполнить следующий код:

W>поскольку после inherited Destroy объект уже уничтожен, и ссылка на его поле (FAfterDestroy) не валидна.

Если честно, я поражаюсь этой легенде. Есть один язык, С++, где особенности реализации множественного наследования вызывают к жизни такое соображение (P.S. «Один» из распространенных). Во всех остальных случаях это соображение, деликатно говоря, не имеет смысла. В дельфе сделали хороший и нужный шаг, выкинув его — и тем не менее, эта легенда ходит по умам и по книгам. Более того, и в яве, и в c#, где вообще виртуальные машины и это соображение звучит пьяным бредом, тупо скопировали «якобы идеологичную реализацию». Настолько тупо, что однажды, кажется в 1.3, я поймал у явы совершенно восхитительный момент:

Дык вот, как работает чудесный код, приведенный выше — делаем new Derived(), вызывает конструктор Base, оттуда виртуальный SomeMethod(), создается и наполняется data, идут возвраты из подпрограмм, и в самом конце, при возврате из конструктора Base в конструктор Derived, наконец-то отрабатывает инициализатор data = null. Зрители в дерьме, оркестр в дерьме, посередине — идеология в белом фраке. Outstanding.

W>IMHO Destroy — это просто метод, вызываемый внутри настоящего деструктора,

Это достаточно неточное утверждение, хотя как напальцевое объяснение сойдет не хуже многих других. Я бы отметил, что в таких вещах IMHO неуместно; надо просто сесть и разобраться, как оно сделано. Впрочем, я бы предложил утешиться тем фактом, что в других распространенных языках «настоящих конструкторов и деструкторов» вообще нет, это чистой воды синтаксический сахар.

От: wallaby
Дата: 20.04.07 04:09
Оценка:

Здравствуйте, Softwarer, Вы писали:

Можно уничтожить объект (Free) и после этого обратиться к его полям. Пока менеджер памяти не занял эту область, это прокатит. Я не разбирался со старой объектной моделью Delphi (кому она нужна?), но в Turbo Pascal’e после вызова унаследованного деструктора объекта уже нет, даже если к нему иногда можно обратиться без AV.

От: Softwarer http://softwarer.ru
Дата: 20.04.07 07:58
Оценка:

Здравствуйте, wallaby, Вы писали:

W>Можно уничтожить объект (Free) и после этого обратиться к его полям. Пока менеджер памяти не занял эту область, это прокатит.

Можно. Разумеется, с пониманием некоторых особенностей — финализации динамических типов, уничтожения владеемых компонент итп.

W>Я не разбирался со старой объектной моделью Delphi (кому она нужна?),

Вот и не рассказывайте сказки, раз не разбирались.

W> но в Turbo Pascal’e после вызова унаследованного деструктора объекта уже нет, даже если к нему иногда можно обратиться без AV.

Специально для Вас я привел код, в котором деструктор вызывается два раза подряд — и почему-то Ваше глубоко философское «объекта уже нет» ничуть не мешает ему успешно отработать во второй раз.

Мало того, что бы Вы ни затвердили, в Turbo Pascal конструкторы и деструкторы вообще ничем, ни единым байтом не отличались от соответствующих «просто методов». Никто не мешал использовать объект без конструктора или после вызова деструктора. Единственная роль, которую играли эти ключевые слова — позволяли использовать соответствующий метод в «объектном» синтаксис операторов New и Dispose.

Советую Вам — если Вы не хотите и дальше регулярно садиться в лужу — проверять свои утверждения. Turbo Pascal нынче свободный компилятор (по крайней мере, версия 5.5), скачать его, загнать в него вышеприведенный код, поменять ShowMessage на WriteLn и проверить — дело пары минут.

Почему деструктор в Delphi назван?

Деструкторы в Delphi обычно называются «Destroy», однако, насколько я понимаю, вы также можете

  • деструкторы имен по-разному
  • имеют несколько деструкторов

Есть ли причина, почему это было реализовано таким образом? Каковы возможные варианты использования для разных названных/множественных деструкторов?

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

Кроме того, поскольку язык Object Pascal не имеет этих магических операций new/delete, должен быть только некоторый идентификатор для вызова объекта.

Я бы предпочел посмотреть на это ретроспективно.

Объекты стиля «Turbo Pascal with Objects» имеют и то, и другое — вы вызываете «магическую» процедуру Dispose , но явно указываете деструктор для вызова, поскольку сам язык не знал, что выбрать. Аналогично «магическая» процедура New должна была быть снабжена вручную выбранным конструктором.

Однако это нарушает принцип DRY: компилятор знает, что мы вызываем d-tor или c-tor, но все же мы должны дополнительно называть эти функции «New» и «Dispose». В теории, возможно, было предусмотрено разделить выделение памяти и подачу информации и в любом случае объединить их. Но я не думаю, что эта функция действительно использовалась как угодно.

Интересно, что тот же дизайн используется в Apple Objective C. Вы выделяете память для объекта, а после этого вы вызываете конструктор для этого нового экземпляра:

Когда эта модель была оптимизирована для Delphi, было принято несколько решений, чтобы сделать вещи более упрощенными (и унифицированными). Стратегия распределения памяти [de] была перенесена на уровень класса, а не на сайт вызова. Это сделало избыточность обоих вызовов «Новый» и названный конструктор очень контрастным. Нужно было отказаться.

С++/С#/Java выбрана для сохранения на нем специальных ключевых слов на языке, используя перегруженные функции для предоставления различных c-торов. Возможно, это соответствует американскому стилю компьютерных языков.

Однако у Паскаля в его основе есть две идеи: многословие и небольшая лексика. Возможно, их можно отследить на других европейских школьных языках, таких как Scala. Если возможно, ключевые слова должны быть удалены с самого языка и перенесены на внешние модули — библиотеки, которые вы можете добавить или удалить из проекта. И перегруженные функции были введены намного позже на язык, и раннее предпочтение состояло в том, чтобы, конечно, иметь два имени, названных по-разному (самодокументированные).

Эти обе идеи, вероятно, заставили Delphi удалить «магические» процедуры и вывести создание/уничтожение объекта на сайте-вызове только с помощью имен используемых функций. Если вы вызываете MyVar.Destroy , тогда компилятор просматривает объявление .Destroy и знает, что мы удаляем объект. Точно так же известно, что TMyType.CreateXXX(YYY,ZZZ) — это объектная инстанция из-за того, что был объявлен CreateXXX .

Чтобы сделать c-tor и d-tor без имени, как в С++, Delphi пришлось бы ввести еще два ключевых слова на уровень языка, например, С++ New и delete . И в этом нет явного преимущества. По крайней мере, лично мне лучше нравится Delphi.

PS. Я должен был добавить туда одно предположение: мы говорим о реальных языках С++ и Delphi, как это было в 1995 году. Они отображали только ручное управление памятью для объектов, выделенных кучей, без сбора мусора и автоматического пересчета. Вы не могли инициировать уничтожение объекта, назначив переменную указателем nil/NULL.

Почему назван деструктор в Delphi?

Деструкторы в Delphi обычно называют «Уничтожить», однако, насколько я понимаю, вы также можете

  • называть деструкторы по-разному
  • иметь несколько деструкторов

Есть ли причина, почему это было реализовано таким образом? Каковы возможные варианты использования для разных имен / нескольких деструкторов?

1 ответ


Теоретически вы можете вручную вызывать различные деструкторы для освобождения различных внешних ресурсов, таких как прерывание циклов пересчета, удаление или просто закрытие файла и т. Д.

Кроме того, так как язык Object Pascal не имеет этих волшебных операций new / delete, просто должен быть какой-то идентификатор для вызова удаления объекта.

Я бы предпочел взглянуть на это задним числом.

Объекты стиля «Турбо Паскаль с объектами» имеют и то и другое — вы вызываете «волшебную» процедуру Dispose , но явно указываете деструктор для вызова, поскольку сам язык не знал, что выбрать. Аналогично «волшебная» процедура New должна была быть снабжена вручную выбранным конструктором.

Это, однако, нарушает принцип DRY: компилятор знает, что мы вызываем d-tor или c-tor, но все же мы должны дополнительно вызывать эти функции «New» и «Dispose». Теоретически это, вероятно, позволило разделить выделение памяти и подачу информации и объединить их в любом случае. Но я не думаю, что эта функция была широко использована.

Интересно, что тот же дизайн используется в Apple Objective C. Сначала вы выделяете память для объекта, а затем вызываете конструктор для этого нового экземпляра: http://en.wikipedia.org/wiki/Objective-C#Instantiation

Когда эта модель была оптимизирована для Delphi, было принято несколько решений, чтобы сделать вещи более упрощенными (и унифицированными). Стратегия выделения памяти [de] была смещена на уровень класса, а не на сайт вызова. Это сделало избыточность как вызова «New», так и именованного конструктора очень контрастной. Один должен был быть отброшен.

C ++ / C # / Java выбран для сохранения специальных ключевых слов на уровне языка, используя перегруженные функции для предоставления различных c-tors. Возможно, это соответствует стилю компьютерных языков США.

Однако Паскаль по своей сути имеет две идеи: многословие и небольшой словарный запас. Возможно, их можно отследить на других языках европейской школы, таких как Scala. Если возможно, ключевые слова должны быть удалены из самого языка и перемещены во внешние модули — библиотеки, которые вы можете добавить или удалить из проекта. И перегруженные функции были введены намного позже в язык, и в начале было предпочтение иметь два по-разному (самодокументируемых) имени функций.

Обе эти идеи, вероятно, побудили Delphi убрать «магические» процедуры и вывести создание / уничтожение объекта на сайте вызова только по именам используемых функций. Если вы вызываете MyVar.Destroy то компилятор просматривает объявление .Destroy и знает, что мы .Destroy объект. Точно так же он знает, что TMyType.CreateXXX(YYY,ZZZ) является экземпляром объекта из-за способа, CreateXXX был объявлен CreateXXX .

Чтобы сделать c-tor и d-tor безымянными, как в C ++, Delphi пришлось бы ввести еще два ключевых слова на уровне языка, такие как C ++ new и delete . И, похоже, в этом нет явного преимущества. По крайней мере, лично мне больше нравится Delphi.

PS. Я должен был добавить одно предположение: мы говорим о реальных языках C ++ и Delphi примерно в 1995 году. В них было только ручное управление памятью для объектов, выделенных в куче, никакой сборки мусора и автоматического подсчета ссылок. Вы не можете запустить уничтожение объекта, назначив переменную с указателем nil / NULL.

Деструктор

Дестру́ктор — специальный метод класса, служащий для деинициализации объекта (например освобождения памяти).

Содержание

Синтаксис деструктора

  • C++: имя деструктора совпадает с именем класса, перед которым стоит символ тильда (

). См. пример ниже.
D: деструкторы имеют имя

this() .

  • Rust: деструкторы построены на типаже Drop .
  • Delphi: деструкторы имеют ключевое слово destructor и могут иметь любое имя, но обычно именуется как Destroy .
  • Object Pascal: деструкторы имеют ключевое слово destructor и могут иметь любое имя, но обычно именуется как Destroy .
  • Objective-C: деструкторы имеют имя dealloc .
  • Perl: деструкторы имеют имя DESTROY ; в расширении Moose для Perl 5, деструкторы имеют название DEMOLISH .
  • PHP: В PHP 5+, деструкторы имеют имя __destruct . В более ранних версиях PHP деструкторов не было. [1]
  • Python: есть методы с именем __del__ , называемые деструкторами в руководстве по языку Python 2, [2] но на самом деле они являются Финализаторами, как это объявлено в Python 3. [3]
  • Swift: деструкторы имеют имя deinit .
  • Деструктор в Delphi

    Для объявления деструктора в Delphi используется ключевое слово destructor . Имя деструктора может быть любым, но рекомендуется всегда называть деструктор Destroy .

    В Delphi все классы являются потомками, по крайней мере, класса TObject, поэтому, для корректного освобождения памяти, необходимо перекрывать деструктор, используя директиву override .

    В Delphi прямой вызов деструктора используется редко. Вместо него используют метод Free .

    Метод Free вначале проверяет существует ли уничтожаемый объект, а затем вызывает деструктор. Этот прием позволяет избегать ошибок, возникающих при обращении к несуществующему объекту.

    Деструктор в C++

    NameOfClass() — деструктор, имеет имя

    NameOfClass , не имеет входных параметров.

    В данном случае при уничтожении объекта выводит в консоль параметр a .

    Деструктор в Rust

    В блоке impl для структуры Foo реализуется одноимённый метод типажа Drop [4] . В коде ниже создаётся переменная foo . Благодаря умной модели памяти, деструктор будет вызван автоматически и без накладных расходов, как только закончится область видимости переменной.

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

    Деструктор интерфейсов или абстрактных классов обычно делают виртуальным. Такой прием позволяет корректно удалять без утечек памяти, имея лишь указатель на базовый класс [5] .

    Пусть (на C++) есть тип Father и порождённый от него тип Son :

    Нижеприведённый код является некорректным и приводит к утечке памяти.

    Однако, если сделать деструктор Father виртуальным:

    вызов delete object; приведет к последовательному вызову деструкторов

    Понравилась статья? Поделиться с друзьями:
    Кодинг, CSS и SQL