5

我经常发现能够安排代码在离开当前范围时执行很有用。在我之前在 TCL 的生活中,一位朋友创建了一个我们称为 defer 的函数。

它启用了如下代码: set fp [open "x"] defer("close $fp");

在当前范围退出时调用它。主要的好处是无论我如何/在哪里离开范围,它总是被调用。

所以我在 Perl 中实现了类似的东西,但似乎有更简单的方法。欢迎评论批评。

我在 Perl 中的做法:

  • 创建一个全局绑定变量,其中包含要执行的子数组。
  • 每当我想安排在退出时调用 fn 时,我都会使用 local 来更改数组。当我离开当前范围时,Perl 将全局更改为以前的值,因为全局是绑定的,我知道何时发生此值更改并且可以调用列表中的 subs。

实际代码如下。

有一个更好的方法吗?似乎这将是一个普遍需要的能力。

use strict;

package tiescalar;

sub TIESCALAR {
    my $class = shift;

    my $self = {};
    bless $self, $class;
    return $self;
}

sub FETCH {
    my $self = shift;
    return $self->{VAL};
}

sub STORE {
    my $self = shift;
    my $value = shift;

    if (defined($self->{VAL}) && defined($value)) {
    foreach my $s (@{$self->{VAL}}) { &$s; }
    }
    $self->{VAL} = $value;
}

1;

package main;

our $h;
tie($h, 'tiescalar');
$h = [];
printf "1\n";
printf "2\n";

sub main { 
    printf "3\n";
    local $h = [sub{printf "9\n"}];
    push(@$h, sub {printf "10\n";});
    printf "4\n";
    { 
    local $h = [sub {printf "8\n"; }];
    mysub();
    printf "7\n";
    return;
    }
}

sub mysub {
    local $h = [sub {printf "6\n"; }];
    print "5\n";
}

main();

printf "11\n";
4

5 回答 5

4

我想我只是创建一个对象,而不是为此使用 tie。你也可以避免local这种方式。

{
my $defer = Scope::OnExit->new( @subs );
$defer->push( $other_sub ); # and pop, shift, etc

...
}

当变量超出范围时,您有机会在 DESTROY 方法中执行操作。

此外,在您发布的示例中,您需要检查您存储的值是否是代码引用,并且检查 VAL 值是否是数组引用可能是个好主意:

子 TIESCALAR { 祝福 { VAL => [] }, $_[0] }

子商店{
    我的($自我,$价值)=@_;

    carp "只能存储数组引用!" 除非 ref $value eq ref [];

    foreach { @$value } {
        carp "数组中应该只有代码引用"
            除非 ref $_ eq ref sub {}
        }

    foreach ( @{ $self->{VAL}} ) { $_->() }


    $self->{VAL} = $价值;
    }
于 2009-03-22T00:30:47.640 回答
4

好吧,如果您使用词法文件句柄(而不是旧式的裸字文件句柄),您的具体情况已经得到处理。对于其他情况,您始终可以使用对象的 DESTROY 方法,该方法保证在超出范围时会转到零引用:

#!/usr/bin/perl

use strict;
use warnings;

for my $i (1 .. 5) {
    my $defer = Defer::Sub->new(sub { print "end\n" });
    print "start\n$i\n";
}

package Defer::Sub;

use Carp;

sub new {
    my $class = shift;
    croak "$class requires a function to call\n" unless @_;
    my $self  = {
        func => shift,
    };
    return bless $self, $class;
}

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

ETA:我更喜欢 brian 的名字,Scope::OnExit 是一个更具描述性的名字。

于 2009-03-22T00:37:31.167 回答
3

您可能想尝试B::Hooks::EndOfScope

我相信这有效:

   use B::Hooks::EndOfScope; 

   sub foo {
      on_scope_end { 
               $codehere;
      };
      $morecode
      return 1; # scope end code executes.
   }

   foo();
于 2009-03-22T05:10:47.233 回答
1

我认为你想要类似Scope::Guard的东西,但它不能被推送。嗯。

谢谢。

于 2009-03-21T23:44:07.877 回答
1

微不足道,

sub OnLeavingScope::DESTROY { ${$_[0]}->() }

像这样使用:

{
    ...
    my $onleavingscope = bless \sub { ... }, 'OnLeavingScope';
    my $onleavingscope2 = bless \\&whatever, 'OnLeavingScope';
    ...
}

(在使用非封闭匿名子时,只有在解决优化(可以说是一个错误)时才需要对子的引用的额外级别。)

于 2009-03-22T05:27:26.493 回答