Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris

Post-increments, overloaded mutators, and reference counts

by khkramer (Scribe)
on May 16, 2002 at 21:07 UTC ( #167119=perlquestion: print w/replies, xml ) Need Help??
khkramer has asked for the wisdom of the Perl Monks concerning the following question:

I've been poking around for the last couple of days trying to figure out some of the subtleties of global destruction. Digging around Devel::Peek-land is lots of fun, and more than a little educational. Today I found a bug in my code that results in an object's reference count being off by one. I traced the problem back to my overload'ed pre-/post-increment and copy constructor operators.

The problem is that perl increments the reference count when it invokes the copy constructor, but I'm actually trying to return the same reference: I don't want to copy, mutate or re-bless this particular class of objects. I've written some toy code to illustrate my approach and show the problem, and am hoping that someone can suggest another way to tackle this...

My class in question presents a pretty straightforward iterator interface. Each object encapsulates access to a list of records: I overload '++' to move to the next record, and 'bool' to test whether the traversal is complete. As the overload perldocs note, though, using '++' in this way implies a need to overload '=' (the copy constructor), too. Here's a stripped-down illustration:

package Toy_Iterator; use overload '""' => sub { $_[0] }, bool => sub { ! $_[0]->done_p() }, '++' => \&inc, '=' => sub { $_[0] }, ; sub new { my ( $class, $arg ) = @_; my $self = {}; bless $self, $class; $self->set ( $arg ); return $self; } sub set { $_[0]->{_i} = $_[1] } sub get { return $_[0]->{_i} } sub inc { ++$_[0]->{_i} } sub done_p { $_[0]->{_i} > 5 } sub DESTROY { print "Destroying $_[0]\n" } package main; use Devel::Peek; my $a = Toy_Iterator->new ( 2 ); while ( $a++ ) { print "$a -> " . $a->get() . "\n"; } # print "\n"; Dump $a; print "\n"; print "undef'ing \$a...\n"; undef $a; print "done undef'ing\n";

Running this script produces the following output:

Toy_Iterator=HASH(0x80fbc2c) -> 3 Toy_Iterator=HASH(0x80fbc2c) -> 4 Toy_Iterator=HASH(0x80fbc2c) -> 5 undef'ing $a... done undef'ing Destroying Toy_Iterator=HASH(0x80fbc2c)

The problem is indicated by the fact that $a's destructor is not getting called when it should, at the moment of undef'ing, but only later (during global destruction). Uncommenting out the Dump line will show that the REFCNT for the Toy_Iterator object that $a points to is 2, rather than 1 as it should be.

Changing the post-increment in the while statement to a pre-increment eliminates the problem, because the pre-increment overload here doesn't need to call the copy constructor:

Toy_Iterator=HASH(0x80fbc2c) -> 3 Toy_Iterator=HASH(0x80fbc2c) -> 4 Toy_Iterator=HASH(0x80fbc2c) -> 5 undef'ing $a... Destroying Toy_Iterator=HASH(0x80fbc2c) done undef'ing

I've been round and round the overload docs, and tried a bunch of different versions of my '++' and '=' subs. I don't want to clone these objects -- they're pretty heavy-weight and often contain open database handles. Anyway, cloning doesn't seem like the elegant thing to do for this usage case. I thought about writing the copy constructor as a bit of Inline::C that creates the reference using newRV_noinc (or something similar). But that seems like a big kludge.

Any suggestions?

Replies are listed 'Best First'.
Re: Post-increments, overloaded mutators, and reference counts
by Zaxo (Archbishop) on May 17, 2002 at 00:48 UTC

    I tried replacing the package global $a with $c in the faint hope that making the iterator a true lexical would help. That band-aid didn't change matters.

    By your description I'm sure you know this, but Camel 3ed, "The Copy Constructor (=)", pp357-358, describes this exact problem. The solution is, as you mention, to write operator = as a deep clone, duplicating the referred object. Your concern about copying database handles is well-founded, that could lead to terrible trouble.

    I think a redesign is in order. You could lighten the iterator by moving its data and low level implementation into the partner class. Or you could lighten tha partner class by constructing it of references to handles, the "Another Layer of Indirection" approach.

    Good Luck!

    After Compline,

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://167119]
Approved by VSarkiss
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others rifling through the Monastery: (6)
As of 2017-03-26 12:11 GMT
Find Nodes?
    Voting Booth?
    Should Pluto Get Its Planethood Back?

    Results (315 votes). Check out past polls.