在Ada(2005或2012)中实现相当于java finalize块的最佳实践

时间:2011-01-26 11:39:57

标签: ada finalize

Java具有finalize块,允许在块之后执行一些语句 保留(即使引发异常也会执行)。例如:

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

Ada拥有受控对象,可以实现 Finalize 操作 但是没有与java中相同的finalize块。这对于日志记录非常有用 关闭文件,事务等(无需为每个可能的块创建特定的标记类型)。

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

6 个答案:

答案 0 :(得分:2)

正如Adrien在评论中提到的那样,Finalize更类似于析构函数。

为了得到一些近似异常/最终序列的东西你可以沿着这些方向做一些事情(警告,不编译,只键入 - 我们将一起解决任何错误:-)参见Ada RM Exceptions section

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;

答案 1 :(得分:2)

我相信这段代码会按你的要求行事;它使用当前42raise成功打印出return。这是T.E.D建议的实施。

在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;

答案 2 :(得分: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;

答案 3 :(得分:1)

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

然而,恕我直言,这种结构主要是一种破解Java的面向对象系统的方法,对于那些希望在老式程序编程中具有OO结构优势的人来说。即使在Java中,你也总是更好地创建一个合适的类。

所以我认为在Ada中获得该功能的正确方法是制作一个合适的对象,并使你的对象成为Ada.Finalization.Controlled的孩子,这并不是一件容易的事。< / p>

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

答案 4 :(得分:1)

想到另一个答案。它有点沉重(也许比它的价值更麻烦)。但它会给你一些看起来有点像旧的最终块

的东西

我们的想法是将您的“可终结”代码放入任务中。在任务终止之前,您不能离开作用域的任务。因此,您可以将工作代码放在任务中,并将“最终”代码放在任务定义范围之外。父任务将坐在那里等待工作任务结束(以某种方式),然后它会运行“最终”代码,无论它如何结束。缺点是如果任务抛出异常,它将在任务处停止。因此,您仍然不会获得可以抛出异常的行为,并且在“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;

在我看来,也许有一种方法可以用受保护的对象做这样的事情。我会留下那个让其他人知道的。

答案 5 :(得分:0)

让我们正视这个问题。

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

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

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

但这可能有点矫枉过正,并且可能会根据具体情况对这种技术提出反对意见。因此,如果我们对程序的自包含资源管理和使用 C++ 风格的 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。