Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

A warning about overloading assignment methods

by diotalevi (Canon)
on Apr 20, 2005 at 06:07 UTC ( [id://449493]=perlmeditation: print w/replies, xml ) Need Help??

In an overloaded assignment method like -=, it is an error for most code to take a reference to $_[0] and include it in the result. Most often, this code should copy $_[0] into a temporary variable like $self and reference that instead. The rest of this note explains the problem that occurs when you use \$_[0].

The proper way to take a reference to $_[0] and include it in your result object.

sub subtract { my $self = $_[0]; return bless [ \ $self ], "..."; }

The details

In a -= method I wanted to take a reference to the current object and incorporate it into the new value. The normal way to do this is to take a reference to it and return a new object.

sub subtract { return bless [ \ $_[0], ... ], "..."; }

My expectation is that I would get an object containing a reference to another object. That expectation was violated when I got an object with a reference to itself. Due to some magic on the part of overload or the C code in perl implementing overloading, \ $_[0] in the return value is modified into a circular or self-reference.

Expected result

$result = bless( [ \ ... ], "..." );

Actual result showing the self reference

$result = bless( [ \ $result ], "...";

The fix was to reference the $_[0] value under a different name. I opted to use normal object code and said $self = shift so $_[0] would now be in $self. After doing this, I could take references to that value without having perl modify my result into a self-reference.

To debug the original behaviour, I created my correct data structure in my subtract() method, dumped it with Data::Dump::Streamer, returned it, and then dumped it again immediately outside. The observed behaviour was some "magical" alteration was occuring to my data between the time the return() happened and the time the value appeared as a value to be assigned.

I went so far as to run this code under a debugging perl to watch all the opcodes being executed. No additional code was being run and thus no perl code had the opportunity to alter my data. I attempted to debug this under gdb but due to my lack of skill wasn't able to verify that the returned value in (SV*)res in Perl_amagic_call was as I expected it.

Note that I checked that the object I was about to return was some ARRAY(0x12345) and my returned value was also the same ARRAY(0x12345). The trouble is that the contents of the object were being mysteriously modified. It would be incorrect to think that a different object was being constructed or returned by some other unrelated code. I verified that I had the same object inside the subtract() call and outside. I verified the data structure prior to returning it and afterward. The reference to $_[0], not ARRAY(0x12345), was translated into a reference to ARRAY(0x12345)

To summarize, when writing overload functions that may be used for assignment, be sure to not write anything that includes $_[0] by reference. Always copy that out first.

Replies are listed 'Best First'.
Re: A warning about overloading assignment methods
by Zaxo (Archbishop) on Apr 20, 2005 at 06:21 UTC

    If you produce a copy constructor by overloading '=', I believe the reflexive mutators will be autogenerated to do the right thing. There is explanation on pp 7-8,14 of perldoc overload.

    After Compline,
    Zaxo

      I found much of that goop called documentation unintelligible and have implemented methods for -, -=, and =. I only see this problem in -=. The portions covering this part of overload just weren't clear on what they meant. Also, while there were some general warnings about taking references to $_[0] there was nothing about the contents of data structures being magically altered when references to $_[0] were present.
Re: A warning about overloading assignment methods
by demerphq (Chancellor) on Apr 20, 2005 at 07:58 UTC

    I think if i understand this properly you can see the same thing at work without overload. Try this:

    package T; use Data::Dump::Streamer; sub foo { \$_[0] } sub bar { my $x=shift; \$x } my $o=bless [],'T'; Dump($o->foo,$o->bar,$o)->Names(qw(foo bar o))->Out(); __END__ $foo = \$o; $bar = \do { my $v = 'V: $o' }; $o = bless( [], 'T' ); $$bar = $o;

    What happening here is that you seeing the difference between copy semantics (what happens with assignment) and aliasing semantics (what happens when you take a reference to an alias).

    But i know you well enough to know this isnt a surprise to you, so Im wondering if you could explain in more detail perhaps with a minimal case we can run to see the effect you mean.

    ---
    demerphq

      Here's my short test code which demonstrates the data structure prior to returning and how it instamagically turned into a self-ref afterward.

      Here's the data before returning and after returning it from the -= method.

      $expected = bless( \bless( \1, 'T' ), 'T' ); $got = bless( \$got, 'T' );

      And here's the sample code.

      package T; use Data::Dump::Streamer; sub D { my ( $name, $val ) = @_; Dump() ->Purity( 0 ) ->Names( $name ) ->Data( $val ) ->Out(); return; } use overload( '-=' => sub { my $new = bless \ $_[0], "T"; D( new => $new ); return $new; }, '""' => sub { my $self = shift; D( self => $self ); return $$self } ); sub new { my ( $class, $val ) = @_; bless \ $val, $class; } package main; $o = T->new( 1 ); $o -= 2; "$o";
Re: A warning about overloading assignment methods
by Forsaken (Friar) on Apr 20, 2005 at 07:11 UTC
    I have simply set a rule for myself that in any function/method/subroutine/whatchawannacallit the very first thing I do is assign the contents of @_ to various temporary variables, no matter how simple the function may be. One simple my($this, $that, @therest) = @_; line will save so many headaches later on, not to mention the way it improves readibility.
    Remember rule one...
      There are occasionally times when you wish to be extra careful to be using an alias to the same variable your caller is using. I was doing lots of that so it was normal and expected for me to be working on things in @_ directly. That isn't the case for most code.
Re: A warning about overloading assignment methods
by Juerd (Abbot) on Apr 22, 2005 at 14:20 UTC

    It's a very wide spread myth that objects are blessed references. In fact, they are references to blessed variables. The difference? The thing that is blessed.

    I agree that the syntax of bless is misleading. But if you bless \$foo, it is $foo that is actually blessed. This means that you can choose to return \$foo; and it'll still work, even though the scalar assigned for the second reference you're creating is an entirely different one. It's not the reference, but $foo that is blessed.

    @_ is an array of aliases. These are aliases for the variables used in the call. When such a value/variable is mutable, the element in @_ is automatically as mutable, because it's the same thing. This is a danger with using any element of @_ directly, not just when they're overloaded, and not just with bless. Even assignment to one of the elements is dangerous. The lesson is simple: if you want to change something without changing it, copy and change the copy, so the original stays intact.

    Juerd # { site => 'juerd.nl', plp_site => 'plp.juerd.nl', do_not_use => 'spamtrap' }

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://449493]
Approved by Zaxo
Front-paged by ww
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others examining the Monastery: (5)
As of 2024-04-18 19:34 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found