【问题标题】:How can I automatically release resources RAII-style in Perl?如何在 Perl 中自动释放资源 RAII 样式?
【发布时间】:2010-03-23 16:38:18
【问题描述】:

假设我有一个必须释放的资源(例如文件句柄或网络套接字):

open my $fh, "<", "filename" or die "Couldn't open filename: $!";
process($fh);
close $fh or die "Couldn't close filename: $!";

假设process 可能会死。然后代码块提前退出,$fh 不会被关闭。

我可以明确检查错误:

open my $fh, "<", "filename" or die "Couldn't open filename: $!";
eval {process($fh)};
my $saved_error = $@;
close $fh or die "Couldn't close filename: $!";
die $saved_error if $saved_error;

但众所周知,这种代码很难正确编写,并且只会在您添加更多资源时变得更加复杂。

在 C++ 中,我将使用 RAII 创建一个拥有资源的对象,并且其析构函数将释放它。这样,我就不必记住释放资源,并且只要 RAII 对象超出范围,资源清理就会正确进行——即使抛出异常也是如此。不幸的是,在 Perl 中,DESTROY 方法不适合此目的,因为无法保证何时调用它。

是否有 Perlish 方法可以确保即使在出现异常的情况下也能像这样自动释放资源?还是显式错误检查是唯一的选择?

【问题讨论】:

  • Perl 使用引用计数,而不是“真正的”垃圾收集器,因此它是确定性的。您只需要确保您的保护对象不会存储在其他地方。这是通过简单地不将其传递给任何函数或以任何方式使用它来完成的;-)。
  • @Leon 我正要提出相同的论点,但后来我注意到perldoc.perl.org/perltoot.html#DestructorsPerl 关于调用析构函数的正确时间的概念目前没有明确定义,这就是为什么你的析构函数不应依赖于它们何时被调用。
  • 对于它的价值,你的例子是一个糟糕的例子,因为$fh 将在它超出范围时被关闭;词法文件句柄是隐式的 RAII。
  • @Sinan Ünür:理论上你是对的,实际上我还没有看到它不能按预期工作的情况。我认为不确定的部分是不同变量被破坏的顺序。
  • @hobbs:文件句柄并不特别。它们也被引用并有自己的(内置)析构函数。

标签: perl raii


【解决方案1】:

我认为这就是 Scope::Guard 旨在帮助解决的问题。

#!/usr/bin/perl

use strict; use warnings;
use Scope::Guard;

my $filename = 'file.test';

open my $fh, '>', $filename
    or die "Couldn't open '$filename': $!";

{
    my $sg = Scope::Guard->new(
        sub {
            close $fh or die "Could not close";
            warn "file closed properly\n";
        }
    );

    process($fh);
}

sub process { die "cannot process\n" }

然而,正如@Philip 在 cmets 中指出的那样,Scope::Guard 使用了DESTROY 方法,这会导致范围退出代码何时运行的一些不确定性。 Hook::ScopeSub::ScopeFinalizer 等模块看起来也不错,尽管我从未使用过它们。

我很喜欢Try::Tiny 的简洁界面和纯粹的简单性,它将帮助您以正确的方式处理异常:

#!/usr/bin/perl

use strict; use warnings;
use Try::Tiny;

my $filename = 'file.test';

open my $fh, '>', $filename
    or die "Couldn't open '$filename': $!";

try {
    process($fh);
}
catch {
    warn $_;
}
finally {
    close $fh
        and warn "file closed properly\n";
};

sub process { die "cannot process\n" }

【讨论】:

  • 谢谢,这很有趣。但是由于它使用DESTROY 方法,这是否意味着它是垃圾收集器的心血来潮,而不是在范围退出时确定性地工作?我认为Sub::ScopeFinalizerHook::Scope(链接自Scope::Guard)更接近我想要的。
  • Perl 使用引用计数,而不是“真正的”垃圾收集器,因此它是确定性的。
  • 仔细观察,Hook::Scope、Hook::Lexwrap、Sub::ScopeFinalizer 和 Scope::OnExit 使用 sub DESTROY 或 XS SAVEDESTRUCTOR_X。如果有一种方法可以在没有析构函数的情况下做到这一点,我还没有找到。
  • SAVEDESTRUCTOR_X 是一种与 sub DESTROY 截然不同的机制。它与范围直接相关,因此完全具有确定性。它们按 LIFO 顺序执行。
  • @Leon 和 @Philip 这就是我真正喜欢Scope::OnExit 的原因,而且我认为使用该模块可能仍然是 OP 的最佳选择。
【解决方案2】:

我的模块 Scope::OnExit 正是为此而设计的。

【讨论】:

  • 是的,这很好,但它取决于 perltoot 状态不是确定性调用的析构函数。即使 GC“真的”是确定性的,它也没有被记录为确定性的。我宁愿不依赖无证行为。
  • 不,实际上它不依赖于析构函数
【解决方案3】:

词法文件句柄的好处是当它们超出范围时它们会被关闭(并释放)。所以你可以这样做:

{
    # bare block creates new scope
    open my $fh, "<", "filename" or die "Couldn't open filename: $!";
    eval { process($fh) };

    # handle exceptions here

    close $fh or die "Couldn't close filename: $!";
}

# $fh is now out of scope and goes away automagically.

【讨论】:

    猜你喜欢
    • 2012-07-26
    • 1970-01-01
    • 2013-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多