Perl:hash-keys丢失了他们的类信息

时间:2010-10-28 04:41:43

标签: perl class hash key

我有一个方法X.pmdata_x(); 我使用类X的实例作为哈希%seen的键,比如说 现在keys %seen的元素似乎已经忘记了他们的祝福:

use X;

my( $x, $y, %seen );

$x = X->new();
$x->data_x( 1 );

print " x:      ", $x, "\n";
print " x.data: ", $x->data_x(), "\n";

$seen{ $x } = 1;
$y = (keys %seen)[0];

print " y:      ", $y, "\n";
print " y.data: ", $y->data_x(), "\n";

打印:

 x:      X=HASH(0x228fd48)
 x.data: 1
 y:      X=HASH(0x228fd48)
Can't locate object method "data_x" via package "X=HASH(0x228fd48)"
(perhaps you forgot to load "X=HASH(0x228fd48)"?) at test.pl line 15.

$x$y都指向同一地址,但显然keys未复制班级信息。
为什么会这样?

4 个答案:

答案 0 :(得分:7)

他们不仅失去了他们的祝福,他们甚至不再是hashrefs。

您只能在Perl中使用字符串作为哈希键。

所有尚未成为字符串的内容都将被制作成字符串。因此,哈希中的键不再是对象,而是字符串'X = HASH(0x228fd48)'(这是打印时带来的祝福hashref)。无法从该字符串中获取对象(除非您有另一个将这些键映射到原始对象的哈希)。

您需要使用唯一标识符作为哈希键。看来你可以使用当前的字符串版本(基本上是一个内存地址)来至少检查对象的身份(该对象在它活着时似乎没有被移动),但我不确定它会有多稳定是(内部对象的一些实现似乎基于这个想法),并且它没有给你对象相等性检查。

答案 1 :(得分:3)

标准Tie::RefHash模块解决了散列键被字符串化的限制。

NAME
   Tie::RefHash - use references as hash keys

SYNOPSIS
   use Tie::RefHash;
   tie HASHVARIABLE, 'Tie::RefHash', LIST
   tie HASHVARIABLE, 'Tie::RefHash::Nestable', LIST;

   untie HASHVARIABLE;

DESCRIPTION
   This module provides the ability to use references as hash
   keys if you first "tie" the hash variable to this module.
   Normally, only the keys of the tied hash itself are
   preserved as references; to use references as keys in
   hashes-of-hashes, use Tie::RefHash::Nestable, included as
   part of Tie::RefHash.

答案 2 :(得分:1)

只有字符串可以用作哈希键。当您将实例作为键插入时,它将转换为字符串。

选项:

  • 使用一个字符串,该字符串也可用于构造适当的实例
  • 具有对象引用的唯一字符串哈希
  • 将对象序列化为字符串,并在拉出时恢复

最好的办法是保持对象引用的唯一字符串ID的哈希值。 IMHO

答案 3 :(得分:1)

除了其他帖子注释之外,即使你确实获得了一个唯一的对象标识符,如果你没有在哈希键之外的某个地方创建对该对象的引用,那么该对象可能会超出范围,得到垃圾收集,并变得无法访问。

看看这段代码示例及其产生的内容:

use strict;
use warnings; 
$|++;
{ 
    package X; 
    use Moose;

    has side => ( isa => 'Str', is => 'rw', required => 1 );
    has foo => ( isa => 'Int', is => 'rw', required => 1 ); 

    sub DEMOLISH { 
        my ( $self ) = @_ ; 
        printf "Destroyed %i ( %s )\n"  , $self->foo, $self->side;
    }
    __PACKAGE__->meta->make_immutable;
}

{
    package Y;

    my $hash = {};

    for ( 1 .. 5 ){ 
        print "Creating $_ \n";
        my $k  = X->new( foo => $_ , side => 'key' );
        my $v  = X->new( foo  => $_, side => 'value' );

        $hash->{$k} = $v;
        print "Created $_ at $k \n"; 
    }

    for ( keys %$hash ){ 
        print "Emptying Hash slowly, doing key $_ \n";
        delete $hash->{$_};
    }
}

输出:

Creating 1 
Created 1 at X=HASH(0x2597d08) 
Destroyed 1 ( key )
Creating 2 
Created 2 at X=HASH(0x2fca7c0) 
Destroyed 2 ( key )
Creating 3 
Created 3 at X=HASH(0x2fca808) 
Destroyed 3 ( key )
Creating 4 
Destroyed 1 ( value )
Created 4 at X=HASH(0x2597d08) 
Destroyed 4 ( key )
Creating 5 
Created 5 at X=HASH(0x2597d68) 
Destroyed 5 ( key )
Emptying Hash slowly, doing key X=HASH(0x2597d68) 
Destroyed 5 ( value )
Emptying Hash slowly, doing key X=HASH(0x2597d08) 
Destroyed 4 ( value )
Emptying Hash slowly, doing key X=HASH(0x2fca808) 
Destroyed 3 ( value )
Emptying Hash slowly, doing key X=HASH(0x2fca7c0) 
Destroyed 2 ( value )

你会看到每个关键对象都在循环结束时得到GC,因为不再有任何引用它。 你会看到另外一个有趣的事情,我们为“4”生成的关键对象使用与“1”相同的内存地址,所以当我们在哈希中替换它的值时,该值也是GC'd。 :/

解决这个问题相当简单,这是一种方法:

use strict;
use warnings; 
$|++;
{ 
    package X; 
    use Moose;
    use Data::UUID;

    my $ug = Data::UUID->new();

    has side => ( isa => 'Str', is => 'rw', required => 1 );
    has foo => ( isa => 'Int', is => 'rw', required => 1 );
    has uuid => ( isa => 'Str', is => 'rw', required => 1 , builder => '_build_uuid' ); 

    sub _build_uuid { 
        return $ug->create_str();
    }
    sub DEMOLISH { 
        my ( $self ) = @_ ; 
        printf "Destroyed %i ( %s , %s )\n"  , $self->foo, $self->side, $self->uuid;
    }
    __PACKAGE__->meta->make_immutable;
}

{
    package Y;

    my $hash = {};
    my $keys = {};

    for ( 1 .. 5 ){ 
        print "Creating $_ \n";
        my $k  = X->new( foo => $_ , side => 'key' );
        my $v  = X->new( foo  => $_, side => 'value' );

        $keys->{$k->uuid} = $k;
        $hash->{$k->uuid} = $v;
        print "Created $_ at $k \n"; 
    }

    for ( sort keys %$hash ){ 
        print "Emptying Hash slowly, doing key $_ \n";
        delete $hash->{$_};
        delete $keys->{$_};
    }
}

输出:

Creating 1 
Created 1 at X=HASH(0x2a12b58) 
Creating 2 
Created 2 at X=HASH(0x2a0d068) 
Creating 3 
Created 3 at X=HASH(0x2a28960) 
Creating 4 
Created 4 at X=HASH(0x2a28b28) 
Creating 5 
Created 5 at X=HASH(0x2a28c18) 
Emptying Hash slowly, doing key ADD9C702-E254-11DF-A4A3-F48B02F52B7F 
Destroyed 1 ( value , ADD9CA18-E254-11DF-A4A3-F48B02F52B7F )
Destroyed 1 ( key , ADD9C702-E254-11DF-A4A3-F48B02F52B7F )
Emptying Hash slowly, doing key ADD9CBD0-E254-11DF-A4A3-F48B02F52B7F 
Destroyed 2 ( value , ADD9CCD4-E254-11DF-A4A3-F48B02F52B7F )
Destroyed 2 ( key , ADD9CBD0-E254-11DF-A4A3-F48B02F52B7F )
Emptying Hash slowly, doing key ADD9CE5A-E254-11DF-A4A3-F48B02F52B7F 
Destroyed 3 ( value , ADD9CF5E-E254-11DF-A4A3-F48B02F52B7F )
Destroyed 3 ( key , ADD9CE5A-E254-11DF-A4A3-F48B02F52B7F )
Emptying Hash slowly, doing key ADD9D0DA-E254-11DF-A4A3-F48B02F52B7F 
Destroyed 4 ( value , ADD9D1DE-E254-11DF-A4A3-F48B02F52B7F )
Destroyed 4 ( key , ADD9D0DA-E254-11DF-A4A3-F48B02F52B7F )
Emptying Hash slowly, doing key ADD9D38C-E254-11DF-A4A3-F48B02F52B7F 
Destroyed 5 ( value , ADD9D49A-E254-11DF-A4A3-F48B02F52B7F )
Destroyed 5 ( key , ADD9D38C-E254-11DF-A4A3-F48B02F52B7F )