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

Hello monks,

I have a fairly large object framework, many parts of which use a singleton-type class, which contains quite a lot of state, and manages many other types of object.

When I wrote it, I intended that the first call to the constructor would return the "master" reference, subsequent calls would return weak references.

Unfortunately, I didn't test that this did what I expected, indeed perldoc Scalar::Util says

Note that if you take a copy of a scalar with a weakened reference, th +e copy will be a strong reference. my $var; my $foo = \$var; weaken($foo); # Make $foo a weak reference my $bar = $foo; # $bar is now a strong referen +ce
My problem (and a possible solution) is demonstrated in this snippet:
/usr/bin/perl use strict; use Scalar::Util qw/weaken isweak/; my $foo = {}; my $bar = returnweak(); print "returnweak isweak - ".(isweak($bar)? "yes":"no")." - '$bar'\n"; weakenbyref(\$bar); print "weakenbyref isweak - ".(isweak($bar)? "yes":"no")." - '$bar'\n" +; sub returnweak { my $ret = $foo; weaken($ret); return $ret; } sub weakenbyref { my $arg = shift; weaken($$arg = $foo); return; }

which outputs:

returnweak isweak - no - 'HASH(0x606df0)' weakenbyref isweak - yes - 'HASH(0x606df0)'

However passing a reference to the scalar which will contain the objectref is an unusual constructor, and would require changes to all callers, which I'd like to avoid if possible.

Is there a solution which does what I want (create a really weak reference which when copied/assigned, remains weak), and doesn't require a change in all calling code? Something like Scalar::Util::cripple :-)

Thanks



Pete

Replies are listed 'Best First'.
Re: return weak reference
by ikegami (Patriarch) on Sep 01, 2010 at 15:39 UTC
    You have
    -X->+---------+ -Y->| Node | -Z->+---------+ | ^ A B v | +---------+ | Node | +---------+

    Your initial solution was to weaken "X", "Y" and "Z". That doesn't work because the creation of "Y" and "Z" is done outside of your control. You could weaken "B" instead. If for some reason, you can't do that (e.g. if you don't actually control the creation of those nodes), you could use a wrapper.

    -X->+---------+ +---------+ -Y->| Wrapper |-J->| Node | -Z->+---------+ +---------+ | ^ A B v | +---------+ | Node | +---------+
    The wrapper would has a destructor that deletes "A" or "B", breaking the loop, freeing the nodes. This wrapper has already been written for you: Object::Destroyer.
    use Object::Destroyer qw( ); $tree = Object::Destroyer->new($tree);

      Thanks - I discussed the problem with a colleague, he suggested using a wrapper class, I thought some autoload magic could make it transparent - as ever, someone on CPAN already has a solution :-)

      As I understand it, Object::Destroyer will by default call the DESTROY method on the object it guards - this may close resources, but won't cause other references to the object to become undefined, which is what I was trying to achieve. Other packages had a file scoped reference to the object in question, which would become stale, if my understanding is correct.

      #!/usr/bin/perl -w use strict; our $other; my $main = foo->new(); print "undef object: ";<STDIN>; undef $main; print "exit: ";<STDIN>; package foo; use Object::Destroyer; sub new { my $package = shift; my $self = {}; bless $self => $package; $::other = $self; my $ret = Object::Destroyer->new($self); return $ret; } sub DESTROY { my $self =shift; print "DESTROYing $self\n"; #undef $self # doesn't help }

      outputs:

      undef object: DESTROYing foo=HASH(0x817f9d0) exit: DESTROYing foo=HASH(0x817f9d0)

      I had a look at the packages with file scoped references, and realised that I was just creating problems for myself - if I made objects call the constructor in question from a lexical/function scope, pretty much all of my problems go away - lexical scoping takes care of everything.

        As I understand it, Object::Destroyer will by default call the DESTROY method on the object it guards - this may close resources, but won't cause other references to the object to become undefined, which is what I was trying to achieve.

        There won't be anyone using the resource when O::D destroys it, because the O::D will only free it when there noone is using it anymore (i.e. when there are no more references to the O::D object).

        You're saying you want to the object to disappear before everyone's done with it (i.e. while they are still reference to it). Why would you want to do that?

Re: return weak reference
by chromatic (Archbishop) on Sep 01, 2010 at 22:04 UTC
    When I wrote it, I intended that the first call to the constructor would return the "master" reference, subsequent calls would return weak references.

    What's your reasoning for doing so?

    (I can imagine why you might think you need to do so, if you've a background writing C code or maintaining awful C++ code, but I can't imagine a situation in Perl where this is necessary.)

      Yep, you're right - as above, I don't need to do what I was originally after so long as I don't go around making problems for myself :-)

      It's a fairly large framework (~25kloc) for testing SMTP/HTTP etc content scanning proxies. By design, bits of it are quite tightly coupled - the benefit is that it's easy for non-programmers to use - you call Foo::Protocol::SMTP->new(content => spam, ....), the SMTP constructor calls into the rig objects, gets access to machine objects, configures itself appropriately, then when you call $smtp->execute() it connects to RPC daemons on various machines, and does the things you ask for, and collects tcpdumps/logs if it fails.

      Things are futher complicated by the fact that it started life as a god object - I did a serious refactor, but as there were dozens of scripts already using the god object, I had to put in a shim layer to make the old stuff work.