package Class::XSAccessor; use 5.008; use strict; use warnings; use Carp qw/croak/; use Class::XSAccessor::Heavy; use XSLoader; our $VERSION = '1.19'; XSLoader::load('Class::XSAccessor', $VERSION); sub _make_hash { my $ref = shift; if (ref ($ref)) { if (ref($ref) eq 'ARRAY') { $ref = { map { $_ => $_ } @$ref } } } else { $ref = { $ref, $ref }; } return $ref; } sub import { my $own_class = shift; my ($caller_pkg) = caller(); # Support both { getters => ... } and plain getters => ... my %opts = ref($_[0]) eq 'HASH' ? %{$_[0]} : @_; $caller_pkg = $opts{class} if defined $opts{class}; # TODO: Refactor. Move more duplicated code to ::Heavy my $read_subs = _make_hash($opts{getters} || {}); my $set_subs = _make_hash($opts{setters} || {}); my $acc_subs = _make_hash($opts{accessors} || {}); my $lvacc_subs = _make_hash($opts{lvalue_accessors} || {}); my $pred_subs = _make_hash($opts{predicates} || {}); my $ex_pred_subs = _make_hash($opts{exists_predicates} || {}); my $def_pred_subs = _make_hash($opts{defined_predicates} || {}); my $test_subs = _make_hash($opts{__tests__} || {}); my $construct_subs = $opts{constructors} || [defined($opts{constructor}) ? $opts{constructor} : ()]; my $true_subs = $opts{true} || []; my $false_subs = $opts{false} || []; foreach my $subtype ( ["getter", $read_subs], ["setter", $set_subs], ["accessor", $acc_subs], ["lvalue_accessor", $lvacc_subs], ["test", $test_subs], ["ex_predicate", $ex_pred_subs], ["def_predicate", $def_pred_subs], ["def_predicate", $pred_subs] ) { my $subs = $subtype->[1]; foreach my $subname (keys %$subs) { my $hashkey = $subs->{$subname}; _generate_method($caller_pkg, $subname, $hashkey, \%opts, $subtype->[0]); } } foreach my $subtype ( ["constructor", $construct_subs], ["true", $true_subs], ["false", $false_subs] ) { foreach my $subname (@{$subtype->[1]}) { _generate_method($caller_pkg, $subname, "", \%opts, $subtype->[0]); } } } sub _generate_method { my ($caller_pkg, $subname, $hashkey, $opts, $type) = @_; croak("Cannot use undef as a hash key for generating an XS $type accessor. (Sub: $subname)") if not defined $hashkey; $subname = "${caller_pkg}::$subname" if $subname !~ /::/; Class::XSAccessor::Heavy::check_sub_existence($subname) if not $opts->{replace}; no warnings 'redefine'; # don't warn about an explicitly requested redefine if ($type eq 'getter') { newxs_getter($subname, $hashkey); } elsif ($type eq 'lvalue_accessor') { newxs_lvalue_accessor($subname, $hashkey); } elsif ($type eq 'setter') { newxs_setter($subname, $hashkey, $opts->{chained}||0); } elsif ($type eq 'def_predicate') { newxs_defined_predicate($subname, $hashkey); } elsif ($type eq 'ex_predicate') { newxs_exists_predicate($subname, $hashkey); } elsif ($type eq 'constructor') { newxs_constructor($subname); } elsif ($type eq 'true') { newxs_boolean($subname, 1); } elsif ($type eq 'false') { newxs_boolean($subname, 0); } elsif ($type eq 'test') { newxs_test($subname, $hashkey); } else { newxs_accessor($subname, $hashkey, $opts->{chained}||0); } } 1; __END__ =head1 NAME Class::XSAccessor - Generate fast XS accessors without runtime compilation =head1 SYNOPSIS package MyClass; use Class::XSAccessor replace => 1, # Replace existing methods (if any) constructor => 'new', getters => { get_foo => 'foo', # 'foo' is the hash key to access get_bar => 'bar', }, setters => { set_foo => 'foo', set_bar => 'bar', }, accessors => { foo => 'foo', bar => 'bar', }, # "predicates" is an alias for "defined_predicates" defined_predicates => { defined_foo => 'foo', defined_bar => 'bar', }, exists_predicates => { has_foo => 'foo', has_bar => 'bar', }, lvalue_accessors => { # see below baz => 'baz', # ... }, true => [ 'is_token', 'is_whitespace' ], false => [ 'significant' ]; # The imported methods are implemented in fast XS. # normal class code here. As of version 1.05, some alternative syntax forms are available: package MyClass; # Options can be passed as a HASH reference, if preferred, # which can also help Perl::Tidy to format the statement correctly. use Class::XSAccessor { # If the name => key values are always identical, # the following shorthand can be used. accessors => [ 'foo', 'bar' ], }; =head1 DESCRIPTION Class::XSAccessor implements fast read, write and read/write accessors in XS. Additionally, it can provide predicates such as C for testing whether the attribute C exists in the object (which is different from "is defined within the object"). It only works with objects that are implemented as ordinary hashes. L implements the same interface for objects that use arrays for their internal representation. Since version 0.10, the module can also generate simple constructors (implemented in XS). Simply supply the C 'constructor_name'> option or the C ['new', 'create', 'spawn']> option. These constructors do the equivalent of the following Perl code: sub new { my $class = shift; return bless { @_ }, ref($class)||$class; } That means they can be called on objects and classes but will not clone objects entirely. Parameters to C are added to the object. The XS accessor methods are between 3 and 4 times faster than typical pure-Perl accessors in some simple benchmarking. The lower factor applies to the potentially slightly obscure C{foo} = $_[1]}>, so if you usually write clear code, a factor of 3.5 speed-up is a good estimate. If in doubt, do your own benchmarking! The method names may be fully qualified. The example in the synopsis could have been written as C instead of C. This way, methods can be installed in classes other than the current class. See also: the C option below. By default, the setters return the new value that was set, and the accessors (mutators) do the same. This behaviour can be changed with the C option - see below. The predicates return a boolean. Since version 1.01, C can generate extremely simple methods which just return true or false (and always do so). If that seems like a really superfluous thing to you, then consider a large class hierarchy with interfaces such as L. These methods are provided by the C and C options - see the synopsis. C check whether a given object attribute is defined. C is an alias for C for compatibility with older versions of C. C checks whether the given attribute exists in the object using C. =head1 OPTIONS In addition to specifying the types and names of accessors, additional options can be supplied which modify behaviour. The options are specified as key/value pairs in the same manner as the accessor declaration. For example: use Class::XSAccessor getters => { get_foo => 'foo', }, replace => 1; The list of available options is: =head2 replace Set this to a true value to prevent C from complaining about replacing existing subroutines. =head2 chained Set this to a true value to change the return value of setters and mutators (when called with an argument). If C is enabled, the setters and accessors/mutators will return the object. Mutators called without an argument still return the value of the associated attribute. As with the other options, C affects all methods generated in the same C statement. =head2 class By default, the accessors are generated in the calling class. The the C option allows the target class to be specified. =head1 LVALUES Support for lvalue accessors via the keyword C was added in version 1.08. At this point, B. Furthermore, their performance hasn't been benchmarked yet. The following example demonstrates an lvalue accessor: package Address; use Class::XSAccessor constructor => 'new', lvalue_accessors => { zip_code => 'zip' }; package main; my $address = Address->new(zip => 2); print $address->zip_code, "\n"; # prints 2 $address->zip_code = 76135; # <--- This is it! print $address->zip_code, "\n"; # prints 76135 =head1 CAVEATS Probably won't work for objects based on I hashes. But that's a strange thing to do anyway. Scary code exploiting strange XS features. If you think writing an accessor in XS should be a laughably simple exercise, then please contemplate how you could instantiate a new XS accessor for a new hash key that's only known at run-time. Note that compiling C code at run-time a la L is a no go. Threading. With version 1.00, a memory leak has been B. Previously, a small amount of memory would leak if C-based classes were loaded in a subthread without having been loaded in the "main" thread. If the subthread then terminated, a hash key and an int per associated method used to be lost. Note that this mattered only if classes were B loaded in a sort of throw-away thread. In the new implementation, as of 1.00, the memory will still not be released, in the same situation, but it will be recycled when the same class, or a similar class, is loaded again in B thread. =head1 SEE ALSO =over =item * L =item * L =back =head1 AUTHOR Steffen Mueller Esmueller@cpan.orgE chocolateboy Echocolate@cpan.orgE =head1 COPYRIGHT AND LICENSE Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 by Steffen Mueller This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8 or, at your option, any later version of Perl 5 you may have available. =cut