I don't see the point of an extra level of indirection.
I'm ignoring the fact that it should use refaddr instead of norm and that it uses a hashref for $data instead of just %data, as those are implementation details and not really about the data structure philosophy. The point is that for N properties, the object data are kept in one HoH data structure instead of N hashes. Whether this is a benefit (that outweighs the cost) depends.
Consider each of the following:
Initialization: given a hashref of initialization properties, the N-hashes way requires copying over each parameter. The HoH way can use the hashref that was passed in directly (storing it or copying it as appropriate).
(Caveat: my examples are to illustrate the point -- this is not necessarily good/robust style as it doesn't validate parameters, assumes that no bogus initializers are passed, etc.)
# N-hashes code adapted from docs to Class::Std sub BUILD { my ($self, $obj_ID, $arg_ref) = @_; $name{$obj_ID} = $arg_ref->{name} || $default_of{name}; $rank{$obj_ID} = $arg_ref->{rank} || $default_of{rank}; # repeat for N properties } # HoH way sub BUILD { my ($self, $obj_ID, $arg_ref) = @_; $data{$obj_ID} = { %default_of, %$arg_ref }; }
Destruction: The same issue arises during DESTROY. From some of my benchmarking, explicit destruction of each entry in the data-storage hash is one of the most inefficient parts of the inside-out technique. That requires either N calls to delete for N properties, or a single delete of the object hashref in the %data hash.
Thread-cloning: Inside-out objects are only thread-safe under 5.8 with the use of the CLONE subroutine. From my article Threads and fork and CLONE, oh my!), a subroutine like this is needed:
sub CLONE { # fix-up all object ids in the new thread for my $old_id ( keys %REGISTRY ) { # look under old_id to find the new, cloned reference my $object = $REGISTRY{ $old_id }; my $new_id = refaddr $object; # relocate data $NAME{ $new_id } = $NAME{ $old_id }; delete $NAME{ $old_id }; # repeat relocation for N properties # update the weak reference to the new, cloned object weaken ( $REGISTRY{ $new_id } = $REGISTRY{ $old_id } ); delete $REGISTRY{ $old_id }; } return; }
Using the HoH approach, only the %data hash would need to be relocated, rather than N individual hash entries.
All this might beg the question "why not use regular hash-based objects, then?" The answer, of course, is that the inside-out technique works on any type of blessed reference. For example, either of the N-hashes or HoH approach could be used to provide object properties to (or subclass) a class that is implemented as a blessed globref.
Benchmarking:
Just for fun, I tried some benchmarking of these approaches using very simple "create/destroy" cycles of these two styles. One of the biggest drivers of efficiency is whether the HoH-style class actually needs to copy the initialization parameters from the hashref, or whether the hashref passed to new can be directly stored.
Directly stored:
Rate NHashObject HoHObject NHashObject 51694/s -- -43% HoHObject 90761/s 76% --
Store a shallow copy of the initialization hashref:
Rate NHashObject HoHObject NHashObject 50557/s -- -4% HoHObject 52932/s 5% --
This examples above had N equal to 6 properties. For N = 3, the advantage is reversed:
Rate HoHObject NHashObject HoHObject 60857/s -- -4% NHashObject 63625/s 5% --
So, as the number of properties increases, HoH style may offer efficiency gains -- though in practice, I suspect this may depend on how much property validation happens, as that may well swamp these small differences.
-xdg
Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.
In reply to Re^2: Inside-out classes structure
by xdg
in thread Inside-out classes structure
by sh1tn
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |