在Perl中,生成getter和setter而不是硬编码是有缺点的吗?

FMc*_*FMc 7 oop perl module class

在下面的示例模块中,通过将匿名子例程添加到符号表来生成getter和setter.在以这种方式创建方法之后,生成的代码在功能上是否等同于(在行为,速度等方面)具有手动编写的getter和setter的模块,或者这种方法是否具有某种固有的责任?(我已经做了一些基本的速度基准测试,到目前为止还没有发现任何差异.)

package Module;    
use strict;
use warnings;

BEGIN {
    my @attr = qw(author title number);
    no strict 'refs';
    for my $a (@attr){
        *{__PACKAGE__ . "::get_$a"} = sub { $_[0]->{$a}         };
        *{__PACKAGE__ . "::set_$a"} = sub { $_[0]->{$a} = $_[1] };
    }
}

sub new {
    my $class = shift;
    bless { @_ }, $class;
}

1;
Run Code Online (Sandbox Code Playgroud)

Joh*_*usa 8

如果在两种情况下生成的代码都相同,运行时性能应该没有差异.但是,这通常是不可能的,除非您使用字符串eval来创建子例程.例如,您提供的代码:

... = sub { $_[0]->{$a} };
Run Code Online (Sandbox Code Playgroud)

将比您手动编写的代码慢得多:

sub foo { $_[0]->{'foo'} }
Run Code Online (Sandbox Code Playgroud)

只是因为前者必须在将变量$ a作为哈希的键之前获取变量$ a的值,而后者使用常量作为其哈希键.另外,作为旁白,shift通常往往比快$_[0].这是一些基准代码:

use Benchmark qw(cmpthese);

package Foo;

sub manual_shift { shift->{'foo'} }
sub manual_index { $_[0]->{'foo'} }

my $attr = 'foo';

*dynamic_shift = sub { shift->{$attr} };
*dynamic_index = sub { $_[0]->{$attr} };

package main;

my $o = bless { foo => 123 }, 'Foo';

cmpthese(-2, {
  manual_shift  => sub { my $a = $o->manual_shift },
  manual_index  => sub { my $a = $o->manual_index },
  dynamic_shift => sub { my $a = $o->dynamic_shift },
  dynamic_index => sub { my $a = $o->dynamic_index },
});
Run Code Online (Sandbox Code Playgroud)

和我的系统上的结果:

                   Rate dynamic_index  manual_index dynamic_shift  manual_shift
dynamic_index 1799024/s            --           -3%           -4%           -7%
manual_index  1853616/s            3%            --           -1%           -4%
dynamic_shift 1873183/s            4%            1%            --           -3%
manual_shift  1937019/s            8%            4%            3%            --
Run Code Online (Sandbox Code Playgroud)

它们非常接近以至于差异可能会在噪音中消失,但在许多试验中我认为你会发现"手动换档"变体是最快的.但是像所有这样的微基准测试一样,你必须在你的硬件和你的perl版本上测试你的确切场景以确保任何事情.

这里的字符串eval被投入到混合中.

eval "sub eval_index { \$_[0]->{'$attr'} }";
eval "sub eval_shift { shift->{'$attr'} }";
Run Code Online (Sandbox Code Playgroud)

它应该与"手动"变体完全相同,加上或减去统计噪声.我的结果:

                   Rate dynamic_index manual_index dynamic_shift manual_shift eval_shift eval_index
dynamic_index 1820444/s            --          -1%           -2%          -3%        -4%        -5%
manual_index  1835005/s            1%           --           -1%          -2%        -3%        -4%
dynamic_shift 1858131/s            2%           1%            --          -1%        -2%        -3%
manual_shift  1876708/s            3%           2%            1%           --        -1%        -2%
eval_shift    1894132/s            4%           3%            2%           1%         --        -1%
eval_index    1914060/s            5%           4%            3%           2%         1%         --
Run Code Online (Sandbox Code Playgroud)

同样,这些都非常接近,你必须付出巨大的努力,并进行许多试验来整理噪音信号.但是使用常量作为散列键和使用变量(必须首先检索其值)作为散列键之间的区别应该显示出来.(shift优化是一个单独的问题,更有可能在perl的过去或未来版本中以某种方式改变.)


dra*_*tun 7

没有区别因为:

sub Some_package::foo { ... }
Run Code Online (Sandbox Code Playgroud)

只是一个简写:

BEGIN { *Some_package::foo = sub { ... } }
Run Code Online (Sandbox Code Playgroud)

来自perlmod的参考


tse*_*see 6

良好生成的访问器的主要缺点是它们打败了依赖静态分析的工具.例如,IDE的方法自动完成.如果这是一个大项目的一部分,我衷心建议你看看穆斯.它的访问器生成正确(以及更多).它很受欢迎,IDE正在添加支持,因此上述问题将在适当的时候消失.

CPAN上有许多访问器生成器,易于使用并生成适度高效的代码.如果性能是一个问题,那么 - 如果你坚持使用访问器方法 - 你不能比Class :: XSAccessor更快,因为它为访问器使用高度优化的C/XS代码.

滚动自己的访问器生成代码是所有选项中最糟糕的.它永远打败了静态分析,可能很难阅读,并可能引入新的错误.