my $dad= My::Parent->new( name => 'Robert' );
{
my $son= My::Child->new( $dad, name => 'Bobby' );
}
# No direct access to Bobby at this point.
# That triggers garbage collection.
# Bobby and Robert circularly reference each other.
# Robert is still reachable from the outside.
# Bobby does not get destroyed, because we can reach him indirectly:
{
my $son= $dad->GetSon(); # Got Bobby again
}
$dad= My::Parent->new( name => 'Walter' );
# No direct access to Robert, now. Garbage collection runs.
# Robert and Bobby form a loop but both are unreferenced.
# Both get destroyed.
####
package My::Parent;
use My::Outer qw< ::Inner new DESTROY AUTOLOAD >;
use overload '""' => \&GetName;
package My::Child;
use My::Outer qw< ::Inner new DESTROY AUTOLOAD >;
use overload '""' => \&GetName;
####
package My::Inner;
# Usage: use My::Inner qw< _wrap _unwrap _incRefs _decRefs >;
# Defines the using class as an inner class wrapped by some other class
# (the wrapping class must use My::Outer).
# The using class must define the following methods:
# _new: the constructor
# _free: breaks ref cycles if all related inner objects are unref'd
#+ _incRefs: increments inner object's ref count
#+ _decRefs: decrements inner object's ref count, returning result
#+ If using class creates blessed hashes and wants $obj->{refs} to be the ref
#+ count, then it can just import the _incRefs and _decRefs methods.
## (Not: "Using class must create blessed hashes so $obj->{ref},
## $obj->{wrapper} work.")
use Exporter 'import';
BEGIN { our @EXPORT_OK= qw< _wrap _unwrap _incRefs _decRefs >; }
sub _wrap {
my( $in, $class )= @_;
my $out= bless \$in, $class;
$in->_incRefs();
## if( $class ) {
## $in->{wrapper}= $class
## } else {
## $class= $in->{wrapper};
## }
return $out;
}
sub _unwrap {
my( $in )= @_;
if( ! $in->_decRefs() ) {
$in->_free();
}
}
#+ (Added below code)
sub _incRefs {
my( $in )= @_;
$in->{refs}++;
}
sub _decRefs {
my( $in )= @_;
return --$in->{refs};
}
####
# Outer objects: dad son These are wrappers given to others
# | | just to track what is still in-use.
# v v
# Inner objects: _dad <------ _son These have the real data, including
# `------------^ links to other (inner) objects.
####
package My::Child::Inner;
use My::Inner qw< _wrap _unwrap _incRefs _decRefs >;
sub _new {
my( $class, $dad, @args )= @_;
# Replace this with your real constructor!
my $_son= { name => 'Boy', @args };
bless $_son, $class;
$$dad->_adopt( $_son )
if $dad;
return $_son;
}
sub GetName {
my( $_son )= @_;
return $_son->{name};
}
sub GetDad {
my( $_son )= @_;
my $_dad= $_son->{dad};
return undef
if ! $_dad;
# We must wrap objects returned from public methods!
return $_dad->_wrap();
}
# For debugging:
sub DESTROY {
my( $_son )= @_;
warn "DESTROYing son: $_son->{name}\n";
}
####
# Called when Son is no longer externally referenced:
sub _free {
my( $_son )= @_;
my $_dad= $_son->{dad};
if( ! $_dad || ! $_dad->{refs} ) {
# Son can die (and take Dad with him)
# if there is no Dad (or Dad is also unreferenced):
$_son->{dad}= $_dad->{son}= undef; # Break ref cycles!
}
}
####
sub _isRefd {
my( $_son, $hvSeen )= @_;
$hvSeen ||= {};
return 0 # Let first call decide, since we've
if $hvSeen->{0+$_son}++; # circled back to ourselves.
return 1
if $_son->{refs};
my $dads= $_son->{dads};
for my $_dad ( @$dads ) {
return 1
if $_dad->isRefd($hvSeen);
}
return 0;
}
####
sub _free {
my( $_son, $force )= @_;
return
if ! $force && $_son->isRefd();
my $dads= $_son->{dads};
for my $_dad ( @$dads ) {
$_dad->_free( 1 ); # No need to check _isRefd() again.
}
$_son->{dads}= [ ];
}
####
{
my $dad= My::Parent->new( name => 'Sr' );
warn "\$dad=$dad\n";
my $son= My::Child->new( $dad, name => 'Jr' );
warn "\$dad=$dad -> \$son=$son\n";
$dad= My::Parent->new( name => 'Newt' );
warn "\$dad=$dad; Sr -> \$son=$son\n";
warn "Sr no longer referenced, but not destroyed yet.\n";
$dad->Adopt( $son );
warn "\$dad=$dad -> \$son=$son; (Sr destroyed)\n";
My::Child->new( $dad, name => 'Young' );
warn "Young never really referenced, but not destroyed yet.\n";
warn "\$dad=$dad -> Young; \$son=$son\n";
$dad= My::Parent->new( name => 'Fin' );
warn "\$dad=$dad; \$son=$son (Newt -> Young destroyed)\n";
warn "Rest to be destroyed next.\n";
}
warn "Everything destroyed above.\n";
####
$dad=Sr
$dad=Sr -> $son=Jr
$dad=Newt; Sr -> $son=Jr
Sr no longer referenced, but not destroyed yet.
DESTROYing dad: Sr
$dad=Newt -> $son=Jr; (Sr destroyed)
Young never really referenced, but not destroyed yet.
$dad=Newt -> Young; $son=Jr
DESTROYing son: Young
DESTROYing dad: Newt
$dad=Fin; $son=Jr (Newt -> Young destroyed)
Rest to be destroyed next.
DESTROYing son: Jr
DESTROYing dad: Fin
Everything destroyed above.
####
#!/usr/bin/perl -w
use strict;
# Outer objects: dad son These are wrappers given to others
# | | just to track what is still in-use.
# v v
# Inner objects: _dad <------ _son These have the real data, including
# `------------^ links to other (inner) objects.
# Allow this mock-up to be run from a single file:
BEGIN { $INC{'My/Outer.pm'}= $INC{'My/Inner.pm'}= __FILE__; }
package My::Outer;
# Usage: use My::Outer qw< ::Inner new DESTROY AUTOLOAD >;
# Defines the using class as an outer wrapper for the named inner class.
# The inner class must use My::Inner.
sub import {
my( $class, $inner, @methods )= @_;
my $outer= caller();
$inner= $outer . $inner
if $inner =~ /^::/;
for my $meth ( @methods ) {
my $sub= *{ _getGlob( __PACKAGE__, "__$meth" ) }{CODE};
die __PACKAGE__, " does not export $meth"
if ! defined $sub;
*{ _getGlob( $outer, $meth ) }= sub { $sub->( $inner, @_ ) };
}
}
sub _getGlob
{
my( $pkg, $name )= @_;
my $stash= do { no strict 'refs'; \%{$pkg.'::'} };
my $glob= \$stash->{$name};
if( 'GLOB' ne ref $glob ) {
{ no strict 'refs'; ${$pkg.'::'.$name}= undef; }
$glob= \$stash->{$name};
die "Can't coerce optimized STAB entry ($pkg\::$name) into real GLOB"
if 'GLOB' ne ref $glob;
}
return $glob;
}
# Gets exported to 'inner' class as 'new' method:
sub __new {
my( $inner, $class )= splice @_, 0, 2;
return $inner->_new( @_ )->_wrap( $class );
}
# Gets exported to 'inner' class as 'DESTROY' method:
sub __DESTROY {
my( $inner, $out )= @_;
my $in= $$out;
$in->_unwrap();
}
# Gets exported to 'inner' class as 'AUTOLOAD' method:
our $AUTOLOAD;
sub __AUTOLOAD {
my( $inner, $out )= splice @_, 0, 2;
my( $outer, $method )= $AUTOLOAD =~ /^(.+)(?:'|::)(.+)$/
or die "Invalid method name: $AUTOLOAD";
die "Can't call internal method, $method, via external wrapper, $outer"
if $method =~ /^_/;
my $in= $$out;
return $in->$method( @_ );
}
package My::Inner;
# Usage: use My::Inner qw< _wrap _unwrap _incRefs _decRefs >;
# Defines the using class as an inner class wrapped by some other class
# (the wrapping class must use My::Outer).
# The using class must define the following methods:
# _new: the constructor
# _free: breaks ref cycles if all related inner objects are unref'd
# _incRefs: increments inner object's ref count
# _decRefs: decrements inner object's ref count, returning result
# If using class creates blessed hashes and wants $obj->{refs} to be the ref
# count, then it can just import the _incRefs and _decRefs methods.
use Exporter 'import';
BEGIN { our @EXPORT_OK= qw< _wrap _unwrap _incRefs _decRefs >; }
sub _wrap {
my( $in, $class )= @_;
my $out= bless \$in, $class;
$in->_incRefs();
return $out;
}
sub _unwrap {
my( $in )= @_;
if( ! $in->_decRefs() ) {
$in->_free();
}
}
sub _incRefs {
my( $in )= @_;
$in->{refs}++;
}
sub _decRefs {
my( $in )= @_;
return --$in->{refs};
}
package My::Parent;
use My::Outer qw< ::Inner new DESTROY AUTOLOAD >;
use overload '""' => \&GetName;
package My::Child;
use My::Outer qw< ::Inner new DESTROY AUTOLOAD >;
use overload '""' => \&GetName;
package My::Parent::Inner;
use My::Inner qw< _wrap _unwrap _incRefs _decRefs >;
sub _new {
my( $class, @args )= @_;
# Replace this with your real constructor!
my $_dad= { name => 'Pop', @args };
return bless $_dad, $class;
}
sub Adopt {
my( $_dad, $son )= @_;
$_dad->_adopt( $son ? $$son : undef );
return $son;
}
sub _adopt {
my( $_dad, $_son )= @_;
# Orphan $_dad's current son (if any):
for my $_orphan ( $_dad->{son} ) {
$_orphan->{dad}= undef
if $_orphan;
}
if( $_son ) {
# Take custody away from new son's prior dad, if any:
for my $_sire ( $_son->{dad} ) {
$_sire->{son}= undef
if $_sire;
}
$_son->{dad}= $_dad;
}
$_dad->{son}= $_son;
return $_son;
}
sub GetName {
my( $_dad )= @_;
return $_dad->{name};
}
sub GetSon {
my( $_dad )= @_;
my $_son= $_dad->{son};
return undef
if ! $_son;
# We must wrap objects returned from public methods!
return $_son->_wrap();
}
# Called when Dad is no longer externally referenced:
sub _free {
my( $_dad )= @_;
my $_son= $_dad->{son};
if( ! $_son || ! $_son->{refs} ) {
# Murder/suicide pact:
$_son->{dad}= $_dad->{son}= undef; # Break ref cycles!
}
}
# For debugging:
sub DESTROY {
my( $_dad )= @_;
warn "DESTROYing dad: $_dad->{name}\n";
}
package My::Child::Inner;
use My::Inner qw< _wrap _unwrap _incRefs _decRefs >;
sub _new {
my( $class, $dad, @args )= @_;
# Replace this with your real constructor!
my $_son= { name => 'Boy', @args };
bless $_son, $class;
$$dad->_adopt( $_son )
if $dad;
return $_son;
}
sub GetName {
my( $_son )= @_;
return $_son->{name};
}
sub GetDad {
my( $_son )= @_;
my $_dad= $_son->{dad};
return undef
if ! $_dad;
# We must wrap objects returned from public methods!
return $_dad->_wrap();
}
# For debugging:
sub DESTROY {
my( $_son )= @_;
warn "DESTROYing son: $_son->{name}\n";
}
# Called when Son is no longer externally referenced:
sub _free {
my( $_son )= @_;
my $_dad= $_son->{dad};
if( ! $_dad || ! $_dad->{refs} ) {
# Son can die (and take Dad with him)
# if there is no Dad (or Dad is also unreferenced):
$_son->{dad}= $_dad->{son}= undef; # Break ref cycles!
}
}
package main;
{
my $dad= My::Parent->new( name => 'Sr' );
warn "\$dad=$dad\n";
my $son= My::Child->new( $dad, name => 'Jr' );
warn "\$dad=$dad -> \$son=$son\n";
$dad= My::Parent->new( name => 'Newt' );
warn "\$dad=$dad; Sr -> \$son=$son\n";
warn "Sr no longer referenced, but not destroyed yet.\n";
$dad->Adopt( $son );
warn "\$dad=$dad -> \$son=$son; (Sr destroyed)\n";
My::Child->new( $dad, name => 'Young' );
warn "Young never really referenced, but not destroyed yet.\n";
warn "\$dad=$dad -> Young; \$son=$son\n";
$dad= My::Parent->new( name => 'Fin' );
warn "\$dad=$dad; \$son=$son (Newt -> Young destroyed)\n";
warn "Rest to be destroyed next.\n";
}
warn "Everything destroyed above.\n";
__END__
$dad=Sr
$dad=Sr -> $son=Jr
$dad=Newt; Sr -> $son=Jr
Sr no longer referenced, but not destroyed yet.
DESTROYing dad: Sr
$dad=Newt -> $son=Jr; (Sr destroyed)
Young never really referenced, but not destroyed yet.
$dad=Newt -> Young; $son=Jr
DESTROYing son: Young
DESTROYing dad: Newt
$dad=Fin; $son=Jr (Newt -> Young destroyed)
Rest to be destroyed next.
DESTROYing son: Jr
DESTROYing dad: Fin
Everything destroyed above.