我经常安排我的子程序条目:
sub mySub {
my ($self, %opts) = @_;
lock_keys(%opts, qw(count, name));
...
my $name = delete $opts{name};
$self->SUPER::mySub(%opts);
}
允许使用如下命名参数调用sub:
$obj->mySub(count=>1, name=>'foobar');
lock_keys防止使用拼写错误的参数名称调用sub。
最后几行是我使用的另一个常用习惯用法,如果我正在编写一个覆盖超类的方法,我可能会提取特定于子类的参数,然后将调用链接到子类。
这在perl 5.8中运行良好,但在升级到Centos 6(具有perl 5.10.1)后,我开始看到看似随机的错误:
Attempt to delete readonly key 'otherOption' from a restricted hash at xxx.pl line 9.
这些错误并不是一直发生的(即使在相同的子程序中),但它们似乎与调用链的调用链有关,这会导致调用轰炸的子。
另请注意,它们不会发生在perl 5.16(or at least not on ideone)上。
在perl 5.10中导致这些错误的原因是什么?根据{{3}},delete()
仍应在lock_keys之后工作。就像整个哈希以某种方式被锁定一样。
答案 0 :(得分:1)
我在发布SO之前就找到了答案,但是解决方法并不是很好,所以请随意加入更好的解决方案。
此SSCCE表现出问题:
#!/usr/bin/perl
use strict;
use Hash::Util qw(lock_keys);
sub doSomething {
my ($a, $b, %opts) = @_;
lock_keys(%opts, qw(myOption, otherOption));
my $x = delete $opts{otherOption};
}
my %h = (
a=>1,
b=>2
);
foreach my $k (keys %h) {
doSomething(1, 2, otherOption=>$k);
}
似乎问题与作为值传递给命名参数hash的值有关(在我的示例中为%opt)。如果从哈希的键中复制这些值,如上例所示,它会将值标记为只读,以便以后阻止从哈希中删除键。
实际上你可以使用Devel :: Peek
看到这个$ perl -e'
use Devel::Peek;
my %x=(a=>1);
foreach my $x (keys %x) {
my %y = (x => $x);
Dump($x);
Dump(\%y);
}
'
SV = PV(0x22cfb78) at 0x22d1fd0
REFCNT = 2
FLAGS = (POK,FAKE,READONLY,pPOK)
PV = 0x22f8450 "a"
CUR = 1
LEN = 0
SV = RV(0x22eeb30) at 0x22eeb20
REFCNT = 1
FLAGS = (TEMP,ROK)
RV = 0x22f8880
SV = PVHV(0x22d7fb8) at 0x22f8880
REFCNT = 2
FLAGS = (PADMY,SHAREKEYS)
ARRAY = 0x22e99a0 (0:7, 1:1)
hash quality = 100.0%
KEYS = 1
FILL = 1
MAX = 7
RITER = -1
EITER = 0x0
Elt "x" HASH = 0x9303a5e5
SV = PV(0x22cfc88) at 0x22d1b98
REFCNT = 1
FLAGS = (POK,FAKE,READONLY,pPOK)
PV = 0x22f8450 "a"
CUR = 1
LEN = 0
请注意,哈希条目的FLAGS是" READONLY"事实上,变量$ x和%y中相应值的值实际上指向相同的字符串(在上面的示例中PV = 0x22f8450)。似乎Perl 5.10正在努力避免复制字符串,但这样做无意中锁定了整个哈希值。
我使用的解决方法是强制使用字符串副本,如下所示:
foreach my $k (keys %h) {
my $j = "$k";
doSomething(1, 2, otherOption=>$j);
}
这似乎是强制字符串副本的低效方式,并且在任何情况下都很容易忘记,因此欢迎包含更好的解决方法的其他答案。