perl - 需要一个可以正常终止的词法作用域的结束

标签 perl exception lexical

我需要能够将 Action 添加到 Action 可能会消失的词法块的末尾。而且我需要正常抛出异常并能够正常捕获。

不幸的是,在 DESTROY 期间,Perl 的特殊情况异常(exception)是通过在消息中添加“(清理中)”并使它们无法捕获。例如:

{
    package Guard;

    use strict;
    use warnings;

    sub new {
        my $class = shift;
        my $code = shift;
        return bless $code, $class;
    }

    sub DESTROY {
        my $self = shift;
        $self->();
    }
}

use Test::More tests => 2;

my $guard_triggered = 0;

ok !eval {
    my $guard = Guard->new(
#line 24
        sub {
            $guard_triggered++;
            die "En guarde!"
        }
    );
    1;
}, "the guard died";

is $@, "En guarde! at $@ line 24\n",    "with the right error message";
is $guard_triggered, 1,                 "the guard worked";

我希望它通过。目前,该异常完全被 eval 吞并了。

这是用于 Test::Builder2,所以我只能使用纯 Perl。

潜在的问题是我有这样的代码:
{
    $self->setup;

    $user_code->();

    $self->cleanup;
}

即使 $user_code 死了,也必须进行清理,否则 $self 会进入一个奇怪的状态。所以我这样做了:
{
    $self->setup;

    my $guard = Guard->new(sub { $self->cleanup });

    $user_code->();
}

复杂性是因为清理运行任意用户代码,这是该代码将死的用例。我希望该异常可以被守卫捕获并且不会改变。

由于改变堆栈的方式,我避免将所有内容都包装在 eval 块中。

最佳答案

这在语义上合理吗?据我了解,你有这个(伪代码):

try {
    user_code(); # might throw
}
finally {
    clean_up(); # might throw
}

有两种可能:
  • user_code()clean_up()永远不会投入相同的运行,在这种情况下,您可以将其编写为顺序代码而无需任何有趣的保护业务,它会起作用。
  • user_code()clean_up()可能在某个时候,两者都 throw 相同的运行。

  • 如果两个函数都可能抛出,那么您有两个事件异常。我不知道任何可以处理当前抛出的多个事件异常的语言,我相信这是有充分理由的。 Perl 添加 (in cleanup)并使异常无法捕获; C++ calls terminate() , Java drops the original exception silently等等等等。

    如果您刚从 eval 中走出来其中user_code()cleanup()抛出异常,您希望在 $@ 中找到什么?

    通常这表示您需要在本地处理清理异常,可能是通过忽略清理异常:
    try {
        user_code();
    }
    finally {
        try {
            clean_up();
        }
        catch {
            # handle exception locally, cannot propagate further
        }
    }
    

    或者您需要选择一个异常以在两者都抛出时忽略(这是 DVK 的解决方案所做的;它忽略 user_code() 异常):
    try {
        user_code();
    }
    catch {
        $user_except = $@;
    }
    try {
        cleanup();
    }
    catch {
        $cleanup_except = $@;
    }
    die $cleanup_except if $cleanup_except; # if both threw, this takes precedence
    die $user_except if $user_except;
    

    或者以某种方式将两个异常合并为一个异常对象:
    try {
        user_code();
    }
    catch {
        try {
            clean_up();
        }
        catch {
            throw CompositeException; # combines user_code() and clean_up() exceptions
        }
        throw; # rethrow user_code() exception
    }
    clean_up();
    

    我觉得应该有办法避免重复 clean_up()上面例子中的行,但我想不出来。

    简而言之,如果不知道当两个部分都抛出时您认为会发生什么,您的问题就无法得到解答。

    关于perl - 需要一个可以正常终止的词法作用域的结束,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4610445/

    相关文章:

    c++ - 简单解析器的简单方法

    perl - 如何在 perl 中的每个类方法之后踢一个特定的回调子程序?

    perl - 使用 Proc::Daemon::Init 创建一个 pid_file

    perl - 为什么 'last' 在 Perl 中被称为 'last' ?

    c# - 正确使用 stacktrace 进行调试

    Android Instagram分享异常

    perl - 你在你的 Perl 程序中使用异常类吗?为什么或者为什么不?

    java - 为什么 JAVA 中会忽略 ASCII SUB (\u001a)?

    perl - 尝试使用@INC并按需调用函数