如何在Perl中以RAII样式自动释放资源?

如何在Perl中以RAII样式自动释放资源?,perl,raii,Perl,Raii,假设我有一个必须释放的资源(例如文件句柄或网络套接字): open my $fh, "<", "filename" or die "Couldn't open filename: $!"; process($fh); close $fh or die "Couldn't close filename: $!"; 但这类代码很难正确编写,而且只有在添加更多资源时才会变得更加复杂 在C++中,我将使用创建一个拥有资源的对象,而它的析构函数将释放它。这样,我就不必记得释放资源,而且只要RAII

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

open my $fh, "<", "filename" or die "Couldn't open filename: $!";
process($fh);
close $fh or die "Couldn't close filename: $!";
但这类代码很难正确编写,而且只有在添加更多资源时才会变得更加复杂

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

有没有更好的方法确保即使在出现异常的情况下也能像这样自动释放资源?或者显式错误检查是唯一的选择吗?

我认为这就是设计用来帮助解决问题的方法

#!/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在评论中所指出的,
Scope::Guard
使用了
DESTROY
方法,这会对何时运行作用域退出代码产生一些不确定性。像
Hook::Scope
Sub::ScopeFinalizer
这样的模块看起来也不错,尽管我从未使用过它们

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

#!/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" }

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

{
    # 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.
{
#裸块创建新的作用域

打开我的$fh,“我的模块正是为了这个目的而设计的。

谢谢,这很有趣。但是由于它使用了
DESTROY
方法,这难道不意味着它是垃圾收集器一时兴起的,而不是在作用域退出时确定地工作吗?我认为
Sub::ScopeFinalizer
Hook::scope
(链接自
Scope::Guard
)更接近我想要的。Perl使用引用计数,而不是“真正的”垃圾收集器,因此它是确定性的。仔细看,Hook::Scope、Hook::Lexwrap、Sub::ScopeFinalizer和Scope::OnExit要么使用
Sub DESTROY
要么使用XS
SAVEDESTRUCTOR_X
。如果有一种不使用析构函数的方法,我还没有找到。SAVEDESTRUCTOR_X与sub DESTROY是一种非常不同的机制。它直接与作用域相关联,因此完全是确定性的。它们以后进先出的顺序执行。@Leon和@Philip这就是为什么我非常喜欢
scope::OnExit
,我认为使用该模块可能仍然可以最好地服务于OP。Perl使用引用计数,而不是“真正的”garbage collector,所以它是确定性的。您只需确保您的guard对象不会存储在其他地方。只需不将其传递给任何函数或以任何方式使用即可;-)@Leon我正要提出同样的论点,但我注意到:Perl关于调用析构函数的正确时间的概念目前还没有很好的定义,这就是为什么你的析构函数在调用时不应该依赖于它。值得一提的是,你的例子是一个糟糕的例子,因为当它超出范围时,
$fh
将被关闭;词法文件句柄是隐式的。@SinanÜnür:理论上你是对的,在实践中我还没有看到它不能按预期工作的情况。我认为它不确定的部分是不同变量被销毁的顺序。@hobbs:文件句柄并不是特别的。它们也被重新计数并有自己的规则(内置)析构函数。是的,这很好,但它取决于析构函数,perltoot声明的析构函数不是确定性的。即使GC“真的是”确定性的,它也没有被记录为确定性的。我宁愿不依赖于未记录的行为。不,它实际上不依赖于析构函数
{
    # 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.