Справочник по Ada 2005
7.6 Пользовательское определение присваивания и финализации
Три вида действий являются основополагающими для работы с объектами: инициализация, финализация и присваивание. Каждый объект инициализируется, явным образом или по умолчанию, после создания (например, с помощью object_declaration или allocator). Каждый объект финализируется перед уничтожением (например, при выходе из subprogram_body, содержащего object_declaration, или при вызове Unchecked_Deallocation). Операция присваивания используется в assignment_statements, явной инициализации, передаче параметров и других операциях.
Предопределенные определения этих трёх фундаментальных операций предоставляются языком, но управляемый тип даёт пользователю дополнительный контроль над частями этих операций. В частности, пользователь может определить для управляемого типа процедуру Initialize, которая вызывается сразу после обычной инициализации по умолчанию управляемого объекта, процедуру Finalize, которая вызывается непосредственно перед финализацией любого из компонентов управляемого объекта, и процедуру Adjust, которая вызывается в последнем шаге присваивания не-ограниченному управляемому объекту.
Статическая семантика
Существует следующий определённый языком библиотечный пакет:
package Ada.Finalization is
pragma Preelaborate(Finalization);
pragma Remote_Types(Finalization);
pragma Preelaborate(Finalization);
pragma Remote_Types(Finalization);
type Controlled is abstract tagged private;
pragma Preelaborable_Initialization(Controlled);
pragma Preelaborable_Initialization(Controlled);
procedure Initialize (Object : in out Controlled) is null;
procedure Adjust (Object : in out Controlled) is null;
procedure Finalize (Object : in out Controlled) is null;
procedure Adjust (Object : in out Controlled) is null;
procedure Finalize (Object : in out Controlled) is null;
type Limited_Controlled is abstract tagged limited private;
pragma Preelaborable_Initialization(Limited_Controlled);
pragma Preelaborable_Initialization(Limited_Controlled);
procedure Initialize (Object : in out Limited_Controlled) is null;
procedure Finalize (Object : in out Limited_Controlled) is null;
private
... -- не определено языком
end Ada.Finalization;
procedure Finalize (Object : in out Limited_Controlled) is null;
private
... -- не определено языком
end Ada.Finalization;
Управляемый тип является потомком Controlled или Limited_Controlled. Предопределённый оператор "=" типа Controlled всегда возвращает True, так как этот оператор включён в реализацию предопределённого оператора равенства для типов, полученных от Controlled, как объяснено в 4.5.2. Тип Limited_Controlled похож на Controlled, за исключением того, что он ограниченный и не имеет примитивной подпрограммы Adjust.
Тип считается нуждающимся в финализации, если:
- он является управляемым типом, типом задачи или защищённым типом; или
- он имеет компонент, нуждающийся в финализации; или
- он является ограниченным типом, который имеет дискриминант доступа, тип которого нуждается в финализации; или
- он является одним из ряда предопределенных типов, которые явным образом определены как нуждающиеся в финализации.
Динамическая семантика
Во время обработки или вычисления конструкции, которая приводит к инициализации объекта по умолчанию, для каждого управляемого подкомпонента объекта, которому не присвоено начальное значение (как определено в 3.3.1), вызывается Initialize для этого подкомпонента. Аналогично, если объект, который инициализируется по умолчанию в целом, является управляемым, Initialize вызывается для объекта.
Для extension_aggregate, предком которого является ancestor_part, являющийся subtype_mark, обозначающий управляемый подтип, вызывается процедура Initialize предка, если эта процедура не является абстрактной.
Initialize и другие операции инициализации выполняются в произвольном порядке, за исключением следующего. Initialize применяется к объекту после инициализации его подкомпонентов, если таковые имеются (включая как неявную инициализацию, так и вызовы Initialize). Если объект имеет компонент с дискриминантом доступа, ограниченным выражением на объект, Initialize применяется к этому компоненту после любых компонентов, не имеющих таких дискриминантов. Для объекта с несколькими компонентами с таким дискриминантом, Initialize применяется к ним в порядке их component_declarations. Для allocator, любые активации задач следуют за всеми вызовами Initialize.
Когда объекту-мишени с управляемыми частями присваивается значение, при создании или в последующей assignment_statement, операция присваивания выполняется следующим образом:
- Значение объекта-мишени становится присвоенным значением.
- Значение объекта-мишени адаптируется.
Для адаптации значения составного объекта (не-ограниченного), значения компонентов объекта сначала адаптируются в произвольном порядке, а затем, если объект управляемый, вызывается Adjust. Адаптация значения элементарного объекта не оказывает никакого влияния, так же как и адаптация значения составного объекта без управляемых частей.
Для assignment_statement, после того как name и expression были вычислены, и все преобразования (включая проверку ограничений) были выполнены, создаётся анонимный объект, и значение присваивается в него; то есть, применяется операция присваивания. (Присваивание включает адаптацию значения.) Затем целевой объект assignment_statement финализируется. Значение анонимного объекта затем присваивается в объект-мишень assignment_statement. Наконец, анонимный объект финализируется. Как объяснено ниже, реализация может устранить промежуточный анонимный объект, поэтому это описание подразумевает то, которое дано в 5.2, «Операции присваивания».
Требования к реализации
Для aggregate управляемого типа, значение которого присваивается, за исключением assignment_statement, реализация не должна создавать отдельный анонимный объект для aggregate. Значение агрегата должно быть построено непосредственно в целевом объекте присваивания, и Adjust не вызывается для целевого объекта.
Разрешения для реализации
Реализация имеет право ослабить вышеприведённые правила (для не-ограниченных управляемых типов) следующим образом:
- Для assignment_statement, который присваивает объекту значение этого же объекта, реализации не нужно ничего делать.
- Для assignment_statement для не-управляемого типа, реализация может финализировать и присваивать каждый компонент переменной по отдельности (вместо финализации всей переменной и присваивания всего нового значения), если дискриминант переменной не меняется при присваивании.
- Для aggregate или вызова функции, значение которого присваивается в целевой объект, реализации не нужно создавать отдельный анонимный объект, если она может безопасно создать значение aggregate или вызова функции непосредственно в целевом объекте. Аналогично, для assignment_statement, реализация не должна создавать анонимный объект, если присваиваемое значение является результатом оценки name, обозначающего объект (объект-источник), чья память не может перекрываться с целевой. Если объект-источник может перекрываться с целевым объектом, то реализация может избежать необходимости в промежуточном анонимном объекте, используя одно из вышеперечисленных разрешений и выполнить присваивание по одному компоненту за раз (для перекрывающегося присваивания массива) или вообще не выполнять (для присваивания, где целевой и исходный объекты присваивания совпадают).
Кроме того, реализация разрешается опускать неявные вызовы Initialize, Adjust и Finalize, а также связанные с ними операции присваивания для объекта неограниченного управляемого типа, при условии, что:
- любой опущенный вызов Initialize не является вызовом пользовательской процедуры Initialize, и
- любое использование значения объекта после неявного вызова Initialize или Adjust и до любого последующего вызова Finalize для объекта не меняет внешнего эффекта программы, и
- после пропуска таких вызовов и операций любое выполнение программы, выполняющее вызов Initialize или Adjust для объекта или инициализирующее объект с помощью aggregate, также позже выполнит вызов Finalize для объекта и всегда выполнит это до присваивания нового значения объекту.
- операции присваивания, связанные с пропущенными вызовами Adjust, также пропускаются.
Это разрешение распространяется на вызовы Adjust и Finalize, даже если неявные вызовы имеют дополнительные внешние эффекты.