frazap has asked for the wisdom of the Perl Monks concerning the following question:

I came to this Is there an advantage to storing references to objects in an attribute instead of the object itself?. Applying find_cycle from Devel::Cycle in my own script showed that I had a lot of circular references.

I realized most of them were due to storing "external" objects' reference in $self current object, many of these were in fact not needed in methods call. But in some methods I had to use an object received in the constructor.

See for example the code below. The circular reference is suppress with a call to weaken (but the second call to do_something fails).

use strict; use warnings; use Devel::Cycle; package A; sub new { my ( $class, $href ) = @_; my $self = { dbi => $$href{dbi} }; $self->{helper} = Helper->new( helper_function => sub { $self->this_is_from_A(@_ +) } ); bless $self, $class; } sub do_something { my $self = shift; $self->{helper}->called_from_helper(); } sub this_is_from_A { my ( $self, $arg ) = @_; print "$arg\nthis_is_from_A using $self->{dbi}\n"; } package Helper; #use Scalar::Util qw/weaken/; sub new { my $class = shift; my %def = ( a => "something" ); my %arg = ( ref $_[0] eq "HASH" ? ( %def, %{ $_[0] } ) : ( %def, + @_ ) ); my $self = \%arg; bless $self, $class; } sub called_from_helper { my $self = shift; print "called_from_helper\n"; $self->{helper_function}->("param from Helper"); #weaken($self->{helper_function}); } package main; my $a = A->new( { dbi => "some dbi object" } ); $a->do_something(); $a->do_something(); find_cycle($a);

Using a sub ref instead of a method calls to this_is_from_A in the code ref pass with helper_function eliminates the circular reference because the code can access the $href->{dbi} directly.

use strict; use warnings; use Devel::Cycle; package A; sub new { my ( $class, $href ) = @_; my $self = { dbi => $$href{dbi} }; $self->{helper} = Helper->new( helper_function => sub { print "$_[0]\nthis_is_from_A using $$href{dbi}\n" +; } ); bless $self, $class; } sub do_something { my $self = shift; $self->{helper}->called_from_helper(); } =for comment sub this_is_from_A { my ($self, $arg) = @_; print "$arg\nthis_is_from_A using $self->{dbi}\n"; } =cut package Helper; sub new { my $class = shift; my %def = ( a => "something"); my %arg = ( ref $_[0] eq "HASH" ? ( %def, %{ $_[0] } ) : ( %def, + @_ ) ); my $self = \%arg; bless $self, $class; } sub called_from_helper { my $self = shift; print "called_from_helper\n"; $self->{helper_function}->("param from Helper"); } package main; my $a = A->new( { dbi => "some dbi object" } ); $a->do_something(); $a->do_something(); find_cycle($a)

But if the code from this_is_from_A was a long and complicated thing, wouldn't it clutter A->new and make it hard to understand and maintain ? Is there another solution ?

Thanks for any comment

frazap

Update: complete the example 2 (end was missing). Update 2 with the correct lines of code.

Replies are listed 'Best First'.
Re: Avoiding circular references
by haukex (Archbishop) on Dec 06, 2019 at 14:27 UTC

    Note that circular references are not intrinsically bad, only when they become memory leaks are they bad. There are a couple of approaches to handling circular references:

    • Avoid them in the first place - not always practical or possible
    • Use weak references - is most practical in a few situations, such as if there is a clear relationship between the objects such that one will always be destroyed before the other, or in caches
    • Manual cleanup - always works, iff the coder remembers to clean up the references in every situation, which is unfortunately easy to forget or mess up
    • Ignore them - only makes any sense if only a handful of such object are created, they exist for the whole run of the program, and you're not relying on anything during their destruction

    Personally, I prefer weak references. In this case, if your object of type Helper will only exist during the lifetime of the object of type A, then the following is one approach where the Helper object is a "child" of the A object, and a weak reference works fine, because the anonymous sub isn't closing over $self. Weakening $self->{parent} in Helper is fine, because we know the parent object will exist for the entire lifetime of the Helper, and we want the helper to be destroyed when the parent is destroyed.

Re: Avoiding circular references
by haj (Vicar) on Dec 06, 2019 at 13:29 UTC

    Well, the question in the title of the article you quoted was based on a misunderstanding: Objects are references (this has been pointed out by dseroh in the other thread as well). There is no performance penalty if you store an object as an attribute of another object.

    In your example, you are creating a closure over $self and store this sub in a Helper object which you keep as an attribute: This results in a cyclic reference. But the cycle is not on the subroutine reference, it is on $self. So by weakening the sub reference, which only has this one reference in the helper object, you kill it and the second call fails.

    Your alternate solution does not create a circular reference because you are using $_[0] instead of $self: $_[0] is the class name 'A', and not an object.

    So if you don't actually need the object in the closure, as the alternate solution suggests, then just don't pass it into the closure.

Re: Avoiding circular references
by tobyink (Canon) on Dec 06, 2019 at 12:50 UTC

    I'm not really sure what the question is, but if your coderef doesn't need access to $self and only needs access to $dbh, then yes, it's a good idea for it to avoid closing over $self.

    The case where you'd want it to close over $self instead of $dbh would be if there was something like a $self->switch_database method that set a new database to connect to, and you wanted your coderef to be aware of the switch instead of continuing to point to the old database.

      it's a good idea for it to avoid closing over $self.

      [I didn't study the OP's code, so this may or may not be pertinent, but I think it is.]

      When you have an object that calls a callback, it's a good idea to pass the object to the callback as an argument.

      For example, say there's a class that calls a function periodically.

      The following exhibits a circular reference:

      my $timer; $timer = Timer->new( callback => sub { ... if (...) { $timer->cancel; } ... }, period => 5, )

      The following doesn't exhibit a circular reference:

      my $timer = Timer->new( callback => sub { my ($timer) = @_; ... if (...) { $timer->cancel; } ... }, period => 5, )

      The latter requires that Timer passes a reference to itself to the callback.

        Your problem can be solved using something similar to the aforementioned "trick", as shown below.

        I've identified the three changes I've made using "# <-----".

        use strict; use warnings; use Devel::Cycle; package A; sub new { my ( $class, $href ) = @_; my $self = { dbi => $$href{dbi} }; $self->{helper} = Helper->new( helper_function => sub { #$self->this_is_from_A(@_) shift->this_is_from_A(@_) # <----- }, ); bless $self, $class; } sub do_something { my $self = shift; #$self->{helper}->called_from_helper(); $self->{helper}->called_from_helper($self); # <----- } sub this_is_from_A { my ( $self, $arg ) = @_; print "$arg\nthis_is_from_A using $self->{dbi}\n"; } package Helper; sub new { my $class = shift; my %def = ( a => "something" ); my %arg = ( %def, ref $_[0] eq "HASH" ? %{ $_[0] } : @_ ); my $self = \%arg; bless $self, $class; } sub called_from_helper { my $self = shift; print "called_from_helper\n"; #$self->{helper_function}->("param from Helper"); $self->{helper_function}->(@_, "param from Helper"); # <----- } package main; my $a = A->new( { dbi => "some dbi object" } ); $a->do_something(); $a->do_something(); find_cycle($a);
      I would have someting like this working
      use strict; use warnings; use Devel::Cycle; package A; sub new { my ( $class, $href ) = @_; my $self = { dbi => $$href{dbi} }; $self->{helper} = Helper->new( helper_function => sub { my $dbi = $$href{dbi}; return $self->this_is_from_A(@_); } ); bless $self, $class; } sub do_something { my $self = shift; $self->{helper}->called_from_helper(); } sub this_is_from_A { my ($self, $arg) = @_; print "$arg\nthis_is_from_A using $dbi\n"; } package Helper; sub new { my $class = shift; my %def = ( a => "something"); my %arg = ( ref $_[0] eq "HASH" ? ( %def, %{ $_[0] } ) : ( %def, + @_ ) ); my $self = \%arg; bless $self, $class; } sub called_from_helper { my $self = shift; print "called_from_helper\n"; my $coderef = $self->{helper_function}->(); $coderef->("param from Helper"); } package main; my $a = A->new( { dbi => "some dbi object" } ); $a->do_something(); $a->do_something(); find_cycle($a);
      But this does not even compile since $dbi in this_is_from_A is not declared.

        So you pass $dbi as an argument.

        package A; sub new { my ( $class, $href ) = @_; my $self = { dbi => $$href{dbi} }; $self->{helper} = Helper->new( helper_function => sub { my $dbi = $$href{dbi}; return $self->this_is_from_A($dbi, @_); # p +ass $dbi } ); bless $self, $class; } sub do_something { my $self = shift; $self->{helper}->called_from_helper(); } sub this_is_from_A { my ($self, $dbi, $arg) = @_; # r +eceive $dbi print "$arg\nthis_is_from_A using $dbi\n"; }
Re: Avoiding circular references
by jcb (Parson) on Dec 09, 2019 at 01:32 UTC

    Do Helper objects correspond one-to-one with A objects? If so, why are they separate objects? Could Helper instead be a mixin or even merged entirely into A?

    Think very carefully about IS-A and HAS-A relationships. Sometimes circular references are unavoidable in an efficient implementation, but a data model with circular dependencies probably should be rethought.