in reply to Is there an advantage to storing references to objects in an attribute instead of the object itself?

Is there an advantage to storing the large object as a reference to the large object instead of the object itself?

An object in Perl is just an anonymous data structure (it just happens to be blessed) that is referred to by one or more references, so in code that uses the object you'd never really store "the object itself" in a variable, but only a reference. You are free to take as many references to the same object as you like*, but in regards to "storing the object itself", I don't understand the question, and I think a Short, Self-Contained, Correct Example would help.

* However, if you have your "one big object" storing references to your "smaller objects", and your smaller objects referring back to the big one, you've created circular references that will prevent the objects from being garbage-collected, in other words, your program has a memory leak. One way to deal with this is to weaken one of the references on one end with weaken from Scalar::Util. Note that even code like you showed in the reply here will still mean there are circular references.

In the following code, try commenting out the line weaken($self->{big_obj}); and see how the output changes to show the memory leak.

use warnings; use strict; { package Big; sub new { my $class = shift; bless {@_}, $class } sub new_little { my ($self,$id) = @_; push @{$self->{little_objs}}, Little->new(id=>$id, big_obj=>$self); return $self; } sub DESTROY { print "DESTROY ",__PACKAGE__," id=",shift->{id},"\n" } } { package Little; use Scalar::Util qw/weaken/; sub new { my $class = shift; my $self = {@_}; weaken($self->{big_obj}); return bless $self, $class; } sub DESTROY { print "DESTROY ",__PACKAGE__," id=",shift->{id},"\n" } } my $big1 = Big->new(id=>"One")->new_little("Hello"); print "Clearing \$big1...\n"; $big1 = undef; # no more references to object should cause GC my $big2 = Big->new(id=>"Two")->new_little("World"); print "Clearing \$big2...\n"; $big2 = undef; END { print "END\n" }

Minor edits for clarity.

  • Comment on Re: Is there an advantage to storing references to objects in an attribute instead of the object itself?
  • Select or Download Code

Replies are listed 'Best First'.
Re^2: Is there an advantage to storing references to objects in an attribute instead of the object itself?
by nysus (Parson) on Dec 02, 2017 at 19:03 UTC

    OK, I ran your code. Very interesting. Thanks for illustrating this problem for me so clearly. Circular references are one of those concepts I blew off and didn't worry about. It's probably time I started worrying.

    So what's the best solution to my problem? Here's my self-contained code that illustrates what I'm doing:

    #! /usr/bin/env perl use warnings; use strict; package BigObjs; use Moose; has 'little_objs' => ( is => 'rw', isa => 'HashRef', default => sub { +{} } ); # our reader method sub get_little_obj { my ($s, $id) = @_; return $s->little_objs->{$id}; } # add new little objects to our collection sub add_little_obj { my ($s, $little_obj) = @_; # create code ref to reader method in big object $little_obj->get_obj_by_id(sub{$s->get_little_obj(@_)}); $s->little_objs->{$little_obj->id} = $little_obj; } package LittleObjs; use Moose; has 'id' => (is => 'rw'); has 'data' => (is => 'rw'); has 'get_obj_by_id' => (is => 'rw'); sub alter_another_little_object { my ($s, $id) = @_; my $other_little_object = $s->get_obj_by_id->($id); $other_little_object->do_stuff; } sub do_stuff { my $s = shift; print "I'm doing stuff to: " . $s->id . "\n"; } no Moose; my $big = BigObjs->new(); my $little1 = LittleObjs->new(id => 'little1', data => 'foo'); my $little2 = LittleObjs->new(id => 'little2', data => 'bar'); $big->add_little_obj($little1); $big->add_little_obj($little2); $little1->alter_another_little_object('little2');

    So you are saying I need to use sub{$s->get_little_obj(@_)} as an argument to a weaken function?

    $PM = "Perl Monk's";
    $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest";
    $nysus = $PM . ' ' . $MCF;
    Click here if you love Perl Monks

      To reply to your various posts in this thread in one place:

      Circular references are one of those concepts I blew off and didn't worry about. It's probably time I started worrying.

      It's definitely something to keep in mind when building object trees like you are. I tried out some different ways to locate circular references in this node, but applying that to your code just shows a bunch of circular references of Class::MOP::* objects, so I'd guess Moose is wrapping everything up and hiding the actual circular references. Applying it to my code above shows them a little more clearly.

      I stored a reference to a reader method ... it provides encapsulation

      Personally, I don't think hiding the "big object" in a code ref adds much. Consider that the class for the big object should already provide all the necessary encapsulation, and so I think there's nothing wrong with simply providing a getter on the "little object" class that returns the big object. You might just consider making this field read-only.

      ... the weak_ref => 1 attribute property. However, slapping that in breaks my code.

      Yes, I can confirm that in your code putting weak_ref on either the little_objs or get_obj_by_id causes "Can't use an undefined value as a HASH/subroutine reference" errors. Also, if I take your code as shown here, and add a sub DESTROY to track destruction like I showed above, it shows the memory leak. Since adding a level of indirection with the code ref doesn't seem to break my code above, at the moment I'm guessing that it's the combination of Moose's weak_ref and the code ref that is causing the problem, but I haven't investigated this further, because I can fix the problem by doing what I said above, just giving the little object a reference to the big object:

      I'd probably just store a weak reference to the big object directly in the little one, and use it in alter_another_little_object to get the other object:
      #! /usr/bin/perl use warnings; use strict; { package BigObjs; use Moose; has 'little_objs' => (is => 'rw', isa => 'HashRef', default => sub + { {} }); sub get_little_obj { my ($s, $id) = @_; return $s->little_objs->{$id}; } sub add_little_obj { my ($s, $little_obj) = @_; $little_obj->big_obj($s); $s->little_objs->{$little_obj->id} = $little_obj; } } { package LittleObjs; use Moose; has id => (is => 'rw'); has data => (is => 'rw'); has big_obj => (is => 'rw', weak_ref => 1); sub alter_another_little_object { my ($s, $id) = @_; my $other_little_object = $s->big_obj->get_little_obj($id); $other_little_object->do_stuff; } sub do_stuff { my $s = shift; print "I'm doing stuff to: " . $s->id . "\n"; } } my $big = BigObjs->new(); my $little1 = LittleObjs->new(id => 'little1', data => 'foo'); my $little2 = LittleObjs->new(id => 'little2', data => 'bar'); $big->add_little_obj($little1); $big->add_little_obj($little2); $little1->alter_another_little_object('little2'); warn "Done\n";
      ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,

      OK, so everything works if I put in the full object into the attribute with weak_ref in the attribute. Thanks so much! I greatly appreciate your consideration. Onward!

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest";
      $nysus = $PM . ' ' . $MCF;
      Click here if you love Perl Monks

      OK, I see in Moose I have to use the weak_ref => 1 attribute property. However, slapping that in breaks my code. Hmm.

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest";
      $nysus = $PM . ' ' . $MCF;
      Click here if you love Perl Monks