Reputation: 8352
I have 3 classes declared as inside-out Perl classes using Class::Std
. In one of these 3, there's a hash reference stored in $basket{ident $self}
that looks like so (output of Data::Dumper):
$VAR1 = {
'auto' => {
'items' => {
'abc' => bless( do{\(my $o = undef)}, 'Item' )
},
'obj' => bless( do{\(my $o = undef)}, 'Category' )
}
};
I need to take this hash reference and create everything in it again (deep cloning). I tried to use dclone
from Storable
like so:
my $new_basket = dclone $basket{ident $self};
When I print the hashes, I get different memory addresses:
print $new_basket, "\n";
print $basket{ident $self}, "\n";
print $new_basket->{auto}->{items}, "\n";
print $basket{ident $self}{auto}->{items}, "\n";
print $new_basket->{auto}->{items}->{abc}, "\n";
print $basket{ident $self}{auto}->{items}->{abc}, "\n";
this will output:
HASH(0x55d325306a20)
HASH(0x55d325245298)
HASH(0x55d323b35ca8)
HASH(0x55d3243dd968)
Item=SCALAR(0x55d323b45190)
Item=SCALAR(0x55d325306588)
When I don't use dclone
and use my $new_basket = $basket{ident $self}
instead, I get the same memory addresses. When I use my $new_basket = { %{ $basket{ident $self} } }
, I get different addresses only on the first level, which should be a shallow copy. All this seems fine and expected.
So, to me it seems that dclone
actually deep-copied everything because the addresses are different. But when I try to use a method inside Item
like so:
print $new_basket->{auto}->{items}->{abc}->get_added_on();
print $basket{ident $self}{auto}->{items}->{abc}->get_added_on();
I get:
Use of uninitialized value in print at lib/Basket.pm line 231.
2020-05-30
clearly that dclone
works differently than I naively thought.
How should I deep-copy this whole structure? I'd appreciate some help or reference to some article/doc where I can read what's going on here.
One solution is to create the whole structure again using constructors, but I thought I'd save some space and use dclone. That obviously didn't turn out very well.
EDIT: I've been asked to provide a minimal runnable demonstration, here it is:
#!/usr/bin/env perl
use strict;
use warnings;
{
package A;
use Class::Std;
use Data::Dumper;
use Storable qw(dclone);
my %basket :ATTR;
sub BUILD {
my ($self, $ident, $args_ref) = @_;
$basket{$ident}->{auto} = {};
my $c = C->new({ date => q{2020-05-30} });
$basket{$ident}->{auto}->{items}->{abc} = $c;
return;
}
sub deep_clone {
my $self = shift;
print Dumper $basket{ident $self};
# the next line prints "2020-05-30" as expected
print $basket{ident $self}->{auto}->{items}->{abc}->get_added_on();
my $new_basket = dclone $basket{ident $self};
# "Use of uninitialized value in print at ./deep-clone.pl line 35."
print $new_basket->{auto}->{items}->{abc}->get_added_on();
}
}
{
package C;
use Class::Std;
my %added_on :ATTR( :get<added_on> );
sub BUILD {
my ($self, $ident, $args_ref) = @_;
$added_on{$ident} = $args_ref->{date};
return;
}
}
####
my $a = A->new();
$a->deep_clone();
Upvotes: 4
Views: 185
Reputation: 386501
The newly created "C" object was never added to %added_on
.
Your classes will have to provide custom handlers for Storable to handle them.
Added to "A":
sub STORABLE_freeze {
my ($self, $cloning) = @_;
my $ident = ident($self);
return "", {
basket => $basket{$ident},
# Other attributes...
};
}
sub STORABLE_thaw {
my ($self, $cloning, $serialized, $inner) = @_;
my $ident = ident($self);
$basket{$ident} = $inner->{basket};
# Other attributes...
}
Added to "C":
sub STORABLE_freeze {
my ($self, $cloning) = @_;
my $ident = ident($self);
return "", {
added_on => $added_on{$ident},
# Other attributes...
};
}
sub STORABLE_thaw {
my ($self, $cloning, $serialized, $inner) = @_;
my $ident = ident($self);
$added_on{$ident} = $inner->{added_on};
# Other attributes...
}
Then you can use freeze
/thaw
/dclone
without problem.
sub deep_clone {
my $self = shift;
#print Dumper $basket{ident $self};
CORE::say $basket{ ident $self }{auto}{items}{abc}->get_added_on();
my $clone = dclone($self);
#print Dumper $basket{ident $self};
CORE::say $basket{ ident $clone }{auto}{items}{abc}->get_added_on();
}
Upvotes: 2