Лучшая практика реализации в Ada (2005 или 2012) эквивалента блока финализации java

В Java есть блок finalize, который позволяет выполнять некоторые операторы после выхода из блока (выполняется, даже если возникает исключение). Пример:

try {
  ...
} catch (Exception e) {
  ...
} finally {
  ... // any code here
}

В Ada есть контролируемые объекты, которые позволяют реализовать операцию Finalize, но нет эквивалента блока finalize, как в java. Это полезно для регистрации, закрытия файлов, транзакций и т. д. (без необходимости создания определенного типа тегов для каждого возможного блока).

  1. Как бы вы реализовали такой блок finalize в Аде 2005 (с сохранением читабельности кода)?
  2. Планируется ли в Ada 2012 упростить выполнение любого кода финализации?

person ciceron    schedule 26.01.2011    source источник
comment
метод Finalize управляемого объекта больше похож на деструктор. он совсем не похож на блок finalize.   -  person Adrien Plisson    schedule 26.01.2011
comment
Да, но можно как-то смоделировать такое поведение, но тяжеловато.   -  person ciceron    schedule 26.01.2011
comment
Это блок finally, который не имеет ничего общего с вызовом методов finalize.   -  person Gabe    schedule 26.01.2011


Ответы (6)


Я считаю, что этот код сделает то, что вы просите; он успешно выводит 42 с настоящим raise или с return. Это реализация предложения T.E.D.

Протестировано с GCC 4.5.0 на Mac OS X, Darwin 10.6.0.

with Ada.Finalization;
package Finally is

   --  Calls Callee on deletion.
   type Caller (Callee : not null access procedure)
      is new Ada.Finalization.Limited_Controlled with private;

private

   type Caller (Callee : not null access procedure)
      is new Ada.Finalization.Limited_Controlled with null record;

   procedure Finalize (Object : in out Caller);

end Finally;


package body Finally is

   procedure Finalize (Object : in out Caller)
   is
   begin
      Object.Callee.all;
   end Finalize;

end Finally;


with Ada.Text_IO; use Ada.Text_IO;
with Finally;
procedure Finally_Demo is
begin

   declare

      X : Integer := 21;

      --  The cleanup procedure, to be executed when this block is left
      procedure F
      is
      begin
         Put_Line ("X is " & Integer'Image (X));
      end F;

      --  The controlled object, whose deletion will execute F
      F_Caller : Finally.Caller (F'Access);

   begin

      X := 42;

      raise Constraint_Error;

   end;

end Finally_Demo;
person Simon Wright    schedule 26.01.2011
comment
Это может показаться большим количеством кода, но, честно говоря, он действительно возвращает ответ на главный вопрос жизни, Вселенной и всего остального. - person T.E.D.; 27.01.2011
comment
Это Program_Error, чтобы вызвать исключение во время Finalize, возможно, должно быть что-то, чтобы обработать это. С другой стороны, это действительно программная ошибка, может, лучше сломать и сжечь! Интересно, какова семантика блока Java finally? - person Simon Wright; 27.01.2011
comment
Java позволяет вам вызывать исключение в блоке finally. Текущее исключение (если есть) теряется. В некоторых случаях вы получаете еще один блок try/catch для игнорирования этих исключений. - person ciceron; 28.01.2011

Как упоминает Адриан в комментарии, Finalize больше похоже на деструктор.

Чтобы получить что-то похожее на исключение/конечную последовательность, вы можете сделать что-то в этом роде (ВНИМАНИЕ, не скомпилировано, просто набрано -- мы вместе исправим любые ошибки :-) См. также раздел "Исключения" Ada RM.

with Ada.Exceptions;  use Ada.Exceptions;

procedure Do_Something is

   -- Variables and what-not...

   -- In case you have an exception and want to reraise it after you've done
   -- the 'final' processing.
   Exception_Caught : Exception_Occurrence := Null_Occurrence;

begin
   -- You can have some statements, like initializations, here that will not
   -- raise exceptions.  But you don't have to, it can all just go in the
   -- following block. However you want to do it...

   declare
      -- If you need to declare some entities local to a block, put those here.
      -- If not, just omit this declare section.  Be aware, though, that if
      -- you initialize something in here and it raises an exception, the
      -- block's exception handler will not catch it. Such an exception will
      -- propagate out of the whole procedure (unless it has an outermost
      -- exception handler) because you're _not_ in the block's scope yet.

   begin
      -- Main processing that might raise an exception

      ...

   exception
      when E : others =>
         -- Handle any exception that's raised.  If there are specific
         -- exceptions that can be raised, they should be explicitly
         -- handled prior to this catch-all 'others' one.

         -- Save the exception occurrence, i.e. make a copy of it that can
         -- be reraised in the 'Final' section if needed.  (If you want to
         -- reraise for a specific exception, do this in those handlers as
         -- well.
         Save_Occurrence(Exception_Caught, E);

   end;

   -- Final processing. Everything from here to the end of the procedure is
   -- executed regardless of whether an exception was raised in the above
   -- block.  By it including an others handler, it ensured that no exception
   -- will propagate out of this procedure without hitting this 'Final' code.

   -- If an exception was raised and needs to be propagated:
   if Exception_Caught /= Null_Occurrence then
      Reraise_Exception(Exception_Caught);
   end if;

end Do_Something;
person Marc C    schedule 26.01.2011
comment
приведенный выше фрагмент кода является стандартным способом обработки исключений в Аде, однако он не совсем имитирует блок finalize: вы не можете повторно вызвать исключение в блоке исключений, иначе код «завершения» не будет выполнен. и если вы позаботитесь о выполнении кода «завершения», то вы потеряли выброшенное исключение, что предотвращает перехват этого исключения на внешнем уровне... - person Adrien Plisson; 26.01.2011
comment
Хорошая точка зрения. Обновлен код, чтобы показать, как сохранить это исключение и впоследствии повторно вызвать его, если это необходимо. - person Marc C; 26.01.2011
comment
Также следует отметить, что оператор return может вывести вас оттуда без запуска кода финализации. Тем не менее, я думаю, что это лучшее, что можно сделать таким образом. - person T.E.D.; 26.01.2011

Предполагая, что вы поняли разницу между ada.finalization и блоком finalize в java, я бы сделал что-то похожее на следующее, что должно иметь тот же эффект.

procedure x is 
begin

  -- some code
  begin
    -- more code (your try)
  exception 
    -- handle exception if necessary (caught exception)
  end;
  -- yet more code which is executed regardless of any exception handling.

end x;
person NWS    schedule 27.01.2011
comment
Только что увидел ответ Marc Cs - похожий на мой :) - person NWS; 27.01.2011
comment
Ага. Тот же комментарий применим и здесь. Оператор return выводит вас прямо из x, не запуская финализирующий код, но это, вероятно, лучшее, что можно сделать, не прибегая к контролируемым типам. - person T.E.D.; 27.01.2011
comment
Что еще более важно, если возникает исключение, оно не будет распространяться. Решение Марка справляется с этим. Предложение Саймона, вероятно, является лучшим, так как оно также заботится о проблеме оператора возврата (но оно довольно тяжелое). - person ciceron; 28.01.2011
comment
Похоже, выбор сводится к тому, хотите ли вы сохранить возможность использовать явный возврат (решение Саймона); или вам это не нужно, или вы готовы кодировать потребность в нем (мое решение). Или стоит ли вообще пытаться имитировать try/catch/finally :-) - person Marc C; 28.01.2011

У Marc C есть правильный подход к попытке эмулировать это в линейном процедурном коде.

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

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

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

person T.E.D.    schedule 26.01.2011

Просто придумал другой ответ. Это немного тяжело (и, возможно, больше проблем, чем оно того стоит). Но это дало бы вам что-то похожее на ваш старый блок finalize.

Идея заключалась бы в том, чтобы поместить ваш «дорабатываемый» код в задачу. Вы не можете покинуть область, в которой объявлена ​​задача, пока задача не завершится. Таким образом, вы можете поместить свой рабочий код в задачу, а ваш «окончательно» код находится за пределами области, в которой определена задача. Родительская задача будет сидеть там и ждать завершения рабочей задачи (так или иначе), а затем он будет запускать код «наконец», независимо от того, как он закончится. Недостатком является то, что если задача выдает исключение, она остановится на задаче. Таким образом, вы все еще не совсем понимаете поведение, при котором вы можете генерировать исключение, и оно будет распространяться автоматически, пока запускается код «финализации». Возможно, вы могли бы вернуть это поведение, добавив рандеву и вторую задачу (это проблема с задачами. Они как картофельные чипсы... вам всегда нужен еще один).

procedure Finalized is
begin
    declare
        task Worker is end Worker;
        task body Worker is begin
            --// Working code in here. Can throw exceptions or whatever. 
            --// Does not matter.
        end Worker;
    begin
    end;

    --// If we get here, we know the task finished somehow (for good or ill)
    --// so the finalization code goes here.

end Finalized;

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

person T.E.D.    schedule 27.01.2011

Давайте рассмотрим эту проблему в перспективе.

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

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

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

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

Я часто сначала был доволен обещанием удобства использования finally, но потом разочаровывался тем, насколько сложным может оказаться код после его использования. Вложение требуется для сложных операций отката, и во многих случаях процесс не является линейным, требуя логики, чтобы решить, как именно выполнить откат, в зависимости от того, как далеко мы продвинулись через инициализацию. Структура вложенных блоков заставляет вас использовать линейную логику. Таким образом, когда вам нужно что-то более сложное, вы серьезно рассматриваете возможность использования goto.

Я прошел через это достаточно раз, чтобы понять, что, как бы ни была аппетитна финализация в стиле ООП с тем, что я упомянул, настоящая попытка и, наконец, могут быть достигнуты в Аде без всех головных болей, как показано в следующем примере. Обратите внимание, что она далека от совершенства, но я думаю, что хорошая стратегия перевешивает плохую. Что мне особенно нравится в этой стратегии, так это то, насколько явным становится процесс финализации, все шаги помечены и проверены на соответствие системе типов Ады, хорошо организованы и просты в управлении. Метод try & finally в стиле C++ рассеивает такой код, а Finalize в Аде скрывает его и затрудняет управление высокоуровневой логикой аварийного переключения.

procedure Proc is
    
    type Init_Stages_All is (Do_Null, Do_Place, Do_Pour, Do_Drink);
    subtype Init_Stages is Init_Stages_All range Do_Place .. Do_Drink;
    Init_Stage : Init_Stages_All := Do_Null;
    
    procedure Initialize_Next is
    begin
        Init_Stage := Init_Stages_All'Succ(Init_Stage);
        case Init_Stage is
            when Do_Place => ...
            when Do_Pour => ...
            when Do_Drink => ...
            when Do_Null => null;
        end case;
    end Initialize_Next;

    procedure Finally is
    begin
        for Deinit_Stage in reverse Init_Stage .. Init_Stages'Last loop
            case Deinit_Stage is
                when Do_Place => ...
                when Do_Pour => ...
                when Do_Drink => ...
            end case;
        end loop;
    end Finally;

begin
    Initialize_Next; -- Do_Place
    ...
    Initialize_Next; -- Do_Pour
    ...
    Initialize_Next; -- Do_Drink
    ...
    Finally;

exception
    when E : others =>
        ...
        Finally;
        raise;

end Proc;

И наконец, эта стратегия также упрощает работу с исключениями финализации. Я бы предложил создать процедуру в Finally, которая вызывается при возникновении исключения, и рекурсивно вызывать finally, манипулируя Init_Stage, а также вставляя туда любую дополнительную логику отработки отказа.

person NiGHTS    schedule 16.02.2021