7

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

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

{
    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 块中。

4

2 回答 2

2

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

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

有两种可能:

  • user_code()并且clean_up()永远不会在同一次运行中抛出,在这种情况下,您可以将其编写为顺序代码而无需任何有趣的守卫业务,它会起作用。
  • user_code()并且clean_up()在某些时候,两者可能会同时进行。

如果两个函数都可能抛出,那么您有两个活动异常。我不知道任何可以处理当前抛出的多个活动异常的语言,我相信这是有充分理由的。Perl 添加(in cleanup)并使异常无法捕获;C++ 调用terminate()Java 默默地丢弃原始异常等等。

如果您刚刚从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()上例中的那一行,但我想不出来。

简而言之,不知道当两个部分都抛出时你认为应该发生什么,你的问题就无法回答。

于 2011-01-06T05:34:59.097 回答
0

更新:下面的方法似乎不像埃里克所说的那样工作!

我留下这个答案,以防有人可以把它变成工作状态。

问题是:

我预计一旦本地变量超出范围,将旧的全局值弹出回全局绑定变量将涉及对 FETCH/STORE 的调用,但不知何故,它只是默默地发生而没有触及绑定机制(该问题与异常处理无关) .


Schwern - 我不是 100% 确定您可以使用 tie 技术(从Abigail 的 Perlmonks 帖子中窃取)用于您的用例 - 这是我尝试做的事情,我认为您正在尝试做

use Test::More tests => 6;

my $guard_triggered = 0;
sub user_cleanup { $guard_triggered++; die "En guarde!" }; # Line 4;
sub TIESCALAR {bless \(my $dummy) => shift}
sub FETCH     { user_cleanup(); }
sub STORE     {1;}
our $guard;
tie $guard => __PACKAGE__; # I don't think the actual value matters

sub x {
    my $x = 1; # Setup
    local $guard = "in x";
    my $y = 2; #user_code;
}

sub x2 {
    my $x = 1; # Setup
    local $guard = "in x2";
    die "you bastard"; #user_code;
}

ok !eval {
    x();
}, "the guard died";
is $@, "En guarde! at $0 line 4.\n",    "with the right error message";
is $guard_triggered, 1,                 "the guard worked";

ok !eval {
    x2();
}, "the guard died";
is $@, "En guarde! at $0 line 4.\n",    "with the right error message";
is $guard_triggered, 2,                 "the guard worked";

输出:

1..6
ok 1 - the guard died
ok 2 - with the right error message
ok 3 - the guard worked
ok 4 - the guard died
ok 5 - with the right error message
ok 6 - the guard worked
于 2011-01-06T02:14:36.327 回答