Perl: хэш-ключи потеряли информацию о своем классе - PullRequest
4 голосов
/ 28 октября 2010

У меня есть пакет 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 не скопировал информацию о классе.
Почему это так?

Ответы [ 4 ]

7 голосов
/ 28 октября 2010

Они не только потеряли свое благословение, они больше не являются даже хеш-референсами.

Вы можете использовать строки как ключи хеша в Perl.

Все, что не является строкой, будет преобразовано в строку. Таким образом, ключ в хэше больше не является объектом, а строкой «X = HASH (0x228fd48)» (именно так выглядит благословленный хэш-рефлекс при печати). Невозможно получить объект обратно из этой строки (если у вас нет другого хеша, который сопоставляет эти ключи с исходными объектами).

Вместо этого вам необходимо использовать уникальный идентификатор в качестве хеш-ключа. Похоже, что вы можете использовать текущую строковую версию (которая в основном является адресом памяти), чтобы хотя бы проверить идентичность объекта (кажется, что объект не перемещается, пока он жив), но я не уверен, насколько стабильным он будет быть (некоторая реализация объектов наизнанку, похоже, основана на этой идее), и она не дает вам проверки на равенство объектов.

3 голосов
/ 28 октября 2010

Стандартный модуль 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.
1 голос
/ 28 октября 2010

В дополнение к другим комментариям к сообщениям, даже если вы получите уникальный идентификатор объекта, если вы не создадите ссылку на этот объект где-то, кроме как в хеш-ключе, объект может выпасть из области видимости, получить мусорсобраны и становятся недоступными.

Взгляните на этот пример кода и то, что он производит:

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'd в конце цикла, так как на него больше нет ссылок.И вы увидите еще одну забавную вещь: ключевой объект, который мы сгенерировали для «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 )
1 голос
/ 28 октября 2010

В качестве хеш-ключей можно использовать только строки. Когда вы вставляете свой экземпляр в качестве ключа, он преобразуется в строку.

Опции:

  • используйте строку, которая также может быть использована для создания соответствующего экземпляра
  • имеет хэш уникальных строк для объекта refs
  • сериализует объект в строку и восстанавливает при извлечении

Лучше всего поддерживать хэш уникальных идентификаторов строк для ссылок на объекты. ИМХО

...