让我们把这个问题放在眼里。
在编程理论中存在对象的创建和销毁以及过程的尝试和最终的概念。两者最终都处理资源管理,但它们以不同的事物为中心。
- 对于对象,我们以对象指针引用的对象和变量的潜在长寿树为中心。
- 对于过程,我们以存在于过程调用范围内的通常临时对象和变量为中心(值得注意的例外是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。