Конструктор связанного скаляра

Если бы у меня был простой связанный скалярный класс, который увеличивается при каждом чтении, я мог бы сделать это так:

package Counter;

use strict;
use warnings;

sub TIESCALAR {
  my $class = shift;
  my $value = 0;

  bless \$value, $class;

  return \$value;
}

sub FETCH {
  my $self = shift;

  my $value = $$self;

  $$self++;

  return $value;
}

sub STORE {
  my $self = shift;
  $$self = shift;
}

1;

Однако для создания переменной-счетчика я должен использовать tie. Я мог бы создать один счетчик и экспортировать его. Но что я действительно хочу сделать, так это сделать так, чтобы это выглядело OO. Кажется, я мог бы создать метод new следующим образом:

sub new {
  my $class = shift;
  my $counter;

  tie $counter, $class;

  return $counter;
}

затем в моем основном сценарии получите два счетчика, выполнив следующие действия:

my $counter1 = Counter->new();
my $counter2 = Counter->new();

Я предполагаю, что это не работает, потому что галстук не выдерживает копию (я где-то читал это в документации), просто нет способа сделать это?

NB. Я знаю, что это всего лишь вопрос стиля, но это выглядело бы правильнее для глаз.


person Joel Berger    schedule 05.02.2011    source источник


Ответы (1)


Магия связи не распространяется на присваивание, потому что она применяется к самой переменной, а не к содержащемуся в ней значению. У вас есть несколько вариантов:

Возврат ссылки:

sub new {tie my $ret, ...; \$ret}

my $counter = Counter->new;

say $$counter;

Присвоение глобусу:

our ($counter);

*counter = Counter->new; # same new as above

say $counter;

Или вы можете передать переменную в конструктор:

sub new {my $class = shift; tie $_[0], $class}

Counter->new(my $counter);

say $counter;

Вы даже можете создать конструктор, который работает с обоими методами:

sub new {
    my $class = shift;
    tie $_[0] => $class;
    \$_[0]
}

our $glob; *glob = Counter->new;

Counter->new(my $lexical);

В последних двух примерах tie передается $_[0] напрямую. Причина этого в том, что элементы @_ являются псевдонимами списка аргументов, поэтому он работает так, как если бы вы набрали my $counter в строке tie.


И, наконец, хотя ваш пример очень ясен и соответствует лучшим практикам, в духе TIMTOWTDI вы можете написать весь свой класс следующим образом:

{package Counter;
    sub TIESCALAR {bless [0]}
    sub FETCH {$_[0][0]++}
    sub STORE {$_[0][0] = $_[1]}
    sub new {tie $_[1] => $_[0]; \$_[1]}
}

Последнее, о чем стоит упомянуть. Хотя ваш вопрос касается связанных переменных, вы также можете использовать перегрузку для этого:

{package Counter;
    use overload fallback => 1, '""' => sub {$_[0][0]++};
    sub new {bless [0]}
}

my $counter = Counter->new;  # overloading survives the assignment

say $counter;

Но вы теряете возможность сбросить счетчик через назначение. Вы можете добавить метод sub set {$_[0][0] = $_[1]} к Counter.

person Eric Strom    schedule 05.02.2011
comment
Также интересно прочитать, что элементы @_ являются псевдонимами и как это здесь имеет значение. Я видел места, где упоминаются псевдонимы, но я не мог понять, почему это должно иметь значение. Спасибо! - person Joel Berger; 05.02.2011