pavelsaman
pavelsaman

Reputation: 8352

Deep cloning of inside-out Perl classes - how to use methods from copied objects?

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

Answers (1)

ikegami
ikegami

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

Related Questions