【问题标题】:Perl: hash-keys have lost their class informationPerl:哈希键丢失了它们的类信息
【发布时间】:2011-05-01 16:32:00
【问题描述】:

我有一个包X.pm,方法是data_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 没有复制班级信息。
为什么呢?

【问题讨论】:

    标签: perl class hash key


    【解决方案1】:

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

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

    所有还不是字符串的东西都会变成字符串。所以散列中的键不再是一个对象,而是字符串 'X=HASH(0x228fd48)' (这是一个祝福的 hashref 打印时的样子)。没有办法从该字符串中取回对象(除非您有另一个哈希将这些键映射到原始对象)。

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

    【讨论】:

    【解决方案2】:

    标准的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.
    

    【讨论】:

      【解决方案3】:

      除了其他帖子 cmets,即使您确实获得了唯一的对象标识符,如果您没有在哈希键以外的其他位置创建对该对象的引用,则该对象可能会超出范围,得到垃圾收集起来,变得无法访问。

      看看这个代码示例及其产生的结果:

      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 )
      

      【讨论】:

        【解决方案4】:

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

        选项:

        • 使用一个也可以用来构造适当实例的字符串
        • 具有对象引用的唯一字符串散列
        • 将对象序列化为字符串,拉出时恢复

        您最好的选择是维护一个唯一字符串 id 的散列到对象引用。恕我直言

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-10-27
          • 1970-01-01
          • 2012-08-30
          相关资源
          最近更新 更多