6

Java 有 finalize 块,它允许在块离开后执行一些语句(即使引发异常也执行)。例子:

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

Ada 具有允许实现Finalize操作的受控对象,但没有与 java 中等效的 finalize 块。这对于记录、关闭文件、事务等很有用(无需为每个可能的块创建特定的标记类型)。

  1. 您将如何在 Ada 2005 中实现这样的 finalize 块(同时保持代码可读性)?
  2. Ada 2012 中是否有计划允许轻松执行任何终结代码?
4

6 回答 6

2

我相信这段代码会满足你的要求;它成功地打印出现42raisereturn。这是TED建议的实现。

在 Mac OS X、Darwin 10.6.0 上使用 GCC 4.5.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;
于 2011-01-26T22:43:02.833 回答
2

正如 Adrien 在评论中提到的,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;
于 2011-01-26T13:53:09.990 回答
2

假设您已经了解 ada.finalization 和 java 中的 finalize 块之间的区别,我会做类似以下的事情,这应该具有相同的效果。

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;
于 2011-01-27T13:48:47.353 回答
1

刚刚想到另一个答案。它有点重(也许比它值得的麻烦更多)。但它会给你一些看起来有点像你旧的 finalize 块的东西

我们的想法是将您的“可终结”代码放入任务中。在任务终止之前,您不能离开声明任务的范围。所以你可以把你的工作代码放在任务中,而你的“最终”代码就在定义任务的范围之外。父任务会坐在那里等待工作任务结束(一种或另一种方式),然后无论它如何结束,它都会运行“最终”代码。缺点是如果任务抛出异常,它将在任务处停止。所以你仍然没有完全理解你可以抛出异常的行为,它会在“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;

在我看来,也可能有一种方法可以对受保护的对象执行类似的操作。我会把那个留给其他人弄清楚。

于 2011-01-27T14:53:40.790 回答
1

Marc C 有正确的方法来尝试在直线过程代码中模拟它。

然而,恕我直言,这种结构主要是绕过 Java 的 OO 系统的一种方法,对于那些想要在老式过程编程中获得 OO 的结构优势之一的人来说。即使在 Java 中,您几乎总是最好创建一个适当的类。

因此,我认为在 Ada 中获得该功能的正确方法是制作一个正确的对象,并使您的对象成为Ada.Finalization.Controlled.

如果您不想费心创建一个实际对象,您可以创建一个虚拟对象,将您的最终代码放入其中,然后在您希望它运行的块顶部的堆栈上声明它。这样做的缺点是受控类型本身(至少在我上次使用它们时)必须在包级别范围内声明。在这种情况下,您将无法在其中直接引用低级声明的对象。他们声称他们将在未来的语言修订中解决这个问题,但我最近没有尝试过,看看他们是否这样做。

于 2011-01-26T15:36:31.167 回答
0

让我们把这个问题放在眼里。

在编程理论中存在对象的创建和销毁以及过程的尝试和最终的概念。两者最终都处理资源管理,但它们以不同的事物为中心。

  • 对于对象,我们以对象指针引用的对象和变量的潜在长寿树为中心。
  • 对于过程,我们以存在于过程调用范围内的通常临时对象和变量为中心(值得注意的例外是main

现在,根据过程,我们可能需要生成一系列资源,如果过程被致命错误中断,则必须以相反的顺序回滚所有生成的资源。通常,通过创建专用于管理其各自资源的对象,最容易做到这一点。Ada.Finalization 在这里变得非常有用。

但这可能是矫枉过正,并且可能会根据具体情况对这种技术提出反对意见。因此,如果我们对过程的自包含资源管理和使用 C++ 风格的finally关键字感兴趣,请考虑以下内容。

最初,我常常对使用finally的便利承诺感到高兴,但对使用它后代码变得多么复杂感到失望。复杂的回滚操作需要嵌套,而且很多时候该过程不是线性的,需要逻辑来根据我们通过初始化完成的程度来准确地决定如何回滚。嵌套块结构使您可以使用线性逻辑。因此,当您需要更复杂的东西时,您正在认真考虑使用goto

我已经经历了足够多的时间来意识到,正如我提到的那样,OOP 风格的 finalize 可以令人开胃,真正的 try & finally 可以在 Ada 中实现,而不会像以下示例中所展示的那样令人头疼。请注意,它远非完美,但我认为这种策略的好处大于坏处。我特别喜欢这个策略的地方是最终确定过程变得多么明确,所有步骤都标记并根据 Ada 的类型系统进行检查,组织良好且易于管理。C++ 风格的 try & finally 分散了这种代码,而 Ada 的 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 中创建一个在引发异常时调用的过程,通过操作 Init_Stage 以及在其中插入任何其他故障转移逻辑来递归调用 finally。

于 2021-02-16T17:34:58.647 回答