Perl: hash-keys have lost their class information
I have a package X.pm
with a method data_x();
%seen
, say.
Now the elements of keys %seen
seem to have forgotten their blessing:
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";
This prints:
x: X=HASH(0x228fd48)
x.data: 1
y: X=HASH(0x228fd48)
Can't locate object method "data_x" via package "X=HASH(开发者_StackOverflow社区0x228fd48)"
(perhaps you forgot to load "X=HASH(0x228fd48)"?) at test.pl line 15.
Both $x
and $y
point to the same address, but apparently keys
did not copy the class info.
They did not only lose their blessing, they are not even hashrefs anymore.
You can only use strings as hash keys in Perl.
Everything that is not already a string will be made into a string. So the key in the hash is not an object anymore, but the string 'X=HASH(0x228fd48)' (which is what a blessed hashref looks like when printed). There is no way to get the object back from that string (unless you have another hash which maps these keys to original objects).
You need to use a unique identifier as the hash key instead. It appears that you can use the current string version (which is basically a memory address) to at least check for object identity (the object does not seem to be moved around while it is alive), but I am not sure how stable that would be (some implementation of inside-out objects seem to be based on this idea, though), and it does not give you object equality checks.
The standard Tie::RefHash
module works around the restriction that hash keys are stringified.
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.
In addition to the other posts comments, even if you do get a unique object identifer, if you don't create a reference to that object somewhere other than in the hash key, the object might fall out of scope, get garbage collected, and become inaccessable.
Take a look at this code sample and what it produces:
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->{$_};
}
}
Outputs:
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 )
You'll see that every single key object got GC'd at the end of the loop due to there no longer being any reference to it. And you'll see an additional fun thing, that the key-object we generated for "4" used the same memory address as "1", so when we replaced its value in the hash, the value was also GC'd. :/
Solving this issue is reasonably simple, and here is one way to do it:
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->{$_};
}
}
Output:
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 )
Only strings can be used as hash keys. When you insert your instance as the key it is converted to a string.
Options:
- use a string that can also be used to construct the appropriate instance
- has a hash of unique strings to object refs
- serialize the object to a string, and restore when pulled out
Your best bet is to maintain a hash of unique string ids to object refs. IMHO
精彩评论