tags:

views:

70

answers:

4

Hi there,

I have a package X.pm with a method data_x();
I use instances of class X as keys of a hash %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(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.
Why is that so?

+7  A: 

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.

Thilo
Thanks for that, Thilo
Klaus
The proper way to get the unique identifier of a reference is to use Scalar::Util::refaddr http://search.cpan.org/~gbarr/Scalar-List-Utils/lib/Scalar/Util.pm
friedo
+1  A: 

Only strings can be used as hash keys. When you inserted you instance as the key it became 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

dietbuddha
A: 

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 )
Kent Fredric
+3  A: 

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.
tchrist
That's another great hint, thanks
Klaus