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

I've already discussed this a bit on the chatterbox, but I'm still not satisfied that it isn't possible.

Say I've created an object with some class' new() method, and have references to it in various places. If I then want to replace the object - in all of these places - with another one, the intuitive thing doesn't work:

$a_ref_to_the_object = Class->new;
as it only changes that one reference.

The following hack does work, but only if the object is stored internally as a hash:

%$a_ref_to_the_object = %{Class->new};
However, this assumption is A Bad Thing, as the code will fall over if the internal representation changes. It also will not work if the object is tied.

This would work in a more general way:

sub retarget_ref { my ($old, $new) = @_; ref $old and ref $new or return; local $SIG{__DIE__}; # disable any user error handlers eval { $$old = $$new }; $@ or return; eval { @$old = @$new }; $@ or return; eval { %$old = %$new }; }
but it would still fail if the object was tied, and it pains me to rely on eval()'s exception trapping.

Is there a simpler, more general, or otherwise nicer way?

Replies are listed 'Best First'.
Re: Changing the target of refs of unknown type
by Corion (Patriarch) on Feb 06, 2005 at 21:39 UTC

    The "easy" way to inflict much hard-to-debug trouble upon yourself is with a proxy object that acts like the real thing, but forwards everything to the "real" object. Your "master" object(s) only hand out proxies, and you can switch the master easily then:

    # Untested code, not for production use # see "hard-to-debug trouble" package Proxy; use strict; use vars qw($AUTOLOAD); sub AUTOLOAD { my $self = shift; $AUTOLOAD =~ m!::([^:]+)$! or die __PACKAGE__ . ": Weird method name $AUTOLOAD"; my $method = $1; $self->()->$method(@_); }; sub new { my ($package,$master) = @_; my $self = sub { my $s = shift; if (@_) { $master = shift; }; $master; }; bless $self, $package; $self; };

    This package fails to fake caller(), so debugging will get harder, and it doesn't respect/propagate list/scalar explicitly, which might have unfortunate results for your code too. Use it as follows:

    package Master::Object; sub new { my $package = shift; my $master = $package->create_a_new_object(@_); return Proxy->new($master); }; ... my $main = Master::Object->new(); # set a new master object: my $new_master = Master::Object->create_a_new_object(); $main->($new_master);

    This is enough rope to hang yourself with. You should really rethink why you would want to do this ! :)

Re: Changing the target of refs of unknown type
by gaal (Parson) on Feb 06, 2005 at 21:01 UTC
    Scalar::Util (which I believe is standard in newer perls) provides a reftype function you could use here.

    But please reconsider your design. You are performing action at a distance, which is rarely a good thing. Perhaps you can to use proxy objects in your code instead?

    Update: another indication that something's wrong: $old and $new will have to have the same underlying Perl type. You *might* be able to do some magic with globs. In itself that would be a clever hack; but really this shows that the code calling the retargeting needs to know so much, you may as well add smarts into your constructors. (Ah yes, look into factories too, if you're not familiar with those.)

Re: Changing the target of refs of unknown type
by stvn (Monsignor) on Feb 06, 2005 at 21:01 UTC

    What's wrong with some kind of reset() method which will just restore your object to a virgin state? Do you really need it to be a new instance? If so, can you elaborate on why? This might help in determining the best solution.

    -stvn
Re: Changing the target of refs of unknown type
by Fletch (Bishop) on Feb 06, 2005 at 23:24 UTC

    Rather than clients keeping a reference to the object, you might use something more akin to the Singleton pattern instead. E.g. something along the lines of Foo->current_instance() that you either use every time you need it or keep in a lexical for (say) the scope of a single sub call.

    sub do_bar { my( $a, $b ) = @_; my $cur_foo = Foo->current_instance; ## ## Use $cur_foo . . . ## return $zorch; }

    So each time it's used you'll have the "current" Foo. If you need to change the current instance your private code calls something like Foo->_set_instance( $new_foo ) behind the scenes.

Re: Changing the target of refs of unknown type
by Irrelevant (Sexton) on Feb 06, 2005 at 21:43 UTC
    OK, I looked over my original code, and came up with a better approach. Thanks for your suggestions, anyway.

    I felt it needed to be a new instance as the classes involved are liable to get inherited, so the class of the two references might be different. I was kinda relying on them having the same internal representation if that happened.

    I'm still curious as to whether this is possible. I might fiddle about a bit with globs, and I'll post an update if I get it working.

    I'll save that rope for introducing Pain And Suffering ™ in another project. ;)