Xpoint
   [напомнить пароль]

Различия хранение объектов и ссылок на объекты

Метки: [без меток]
2008-01-13 21:29:59 [обр] Kleo[досье]

День добрый.
Прошу помощи с разъяснением отличий между хранением объектов и ссылок на эти объекты.

Поясню упрощенным примером.
Веб-разработка.
Есть объект-хранитель (objReestr), который представляет собой реестр объектов.
У него есть методы добавления объектов в хранилище и их извлечения.
В хранителе размещены объекты ajax и session, которые, соответственно, отвечают за обработку ajax-запросов и
за работу с текущей сессией посетителя. Объект ajax, в свою очередь, содержит созданный им объект buf, отвечающий за
захват выходного потока.

Так вот, если мы храним в реестре сами объекты, то при вызове из него объекта сессии (допустим нам потребовался ее индентификатор)
уничтожается объект buf, созданный в ajax (который на самом деле должен обрабатываться и уничтожаться в методе DESTROY)

При этом методы объекта-хранителя выглядят так (упрощенный вариант):

Добавление объекта:

sub addObj
{

   my $self     = shift;
   my $obj      = shift;
   my $name     = shift || $ref obj;

   $self->{$name} = {'obj'=>$obj, 'package'=>ref $obj};
   return 1;
}

Получение объекта

sub get
{
   my $self = shift;
   my $name = shift;
   return (exists $self->{$name}) ? $self->{$name}{'obj'} : undef;
}

Если же в хранителе мы храним не объекты, а только ссылки на них, то при вызове из реестра объекта сессии
объект buf продолжает свое безоблачное существование как раз до вызова метода DESTROY, что нам и требуется:

Добавление объекта:

sub addObj
{

   my $self     = shift;
   my $obj      = shift;
   my $name     = shift || $ref obj;

   $self->{$name} = {'obj'=>\$obj, 'package'=>ref $obj};
   return 1;
}

Получение объекта

sub get
{
   my $self = shift;
   my $name = shift;
   return (exists $self->{$name}) ? ${$self->{$name}{'obj'}} : undef;
}

Помогите разобраться, пожалуйста.

спустя 3 часа 3 минуты [обр] Алексей Севрюков(198/1280)[досье]
сообщение промодерировано
Kleo[досье] Отделите мух от котлет. Причем тут AJAX, причем тут сессии?
Если Ваши объекты действительно являются объектами, то это уже ссылки. Ибо в Perl только три типа данных (скаляр, массив и хэш). Все объекты хранятся как ссылка в скаляре.
Передавая же объект как \$obj, Вы передаете ссылку на скаляр, который содержит ссылку на объект.
спустя 17 минут [обр] Алексей Севрюков(198/1280)[досье]
М Перенесено из форума "Программирование::Perl::Модули"
спустя 22 часа [обр] Kleo[досье]
Алексей, спасибо за ответ. Все объекты действительно являются объектами (т.е создаются через конструкцию my $self = bless {}, $class)
Есть объект ajax, который в себе содержит ссылку на объект buf
Есть объект objReestr, который является хранителем различных объектов, и предоставляет доступ к своему содержимому по запросу.
В нем хранится около десятка различных объектов, в том числе сам объект ajax и объект сессий (в примере привел именно сессии, но это может быть любой другой элемент в хранилище)
Порядок создания объектов следующий: objReestr, ajax, buf, session и затем уже все остальные.
Не могу понять, почему, когда я добавляю объект в реестр в виде ссылки на объект, объект buf уничтожается раньше чем надо, а когда использую для хранения ссылку на скаляр, который в свою очередь содержит ссылку на объект, то все работает нормально. Если суть возникшего вопроса не ясна, попробую создать рабочий пример для пояснения (возможно словесного описания просто не достаточно или я сам не точно выражаюсь)
спустя 7 минут [обр] Алексей Севрюков(198/1280)[досье]
Kleo[досье] Создайте минимальный проблемный пример со всеми пакетами, пропишите везде секции BEGIN, DESTROY и END с дебаговым выводом, запустите и посмотрите что выдает. Скорее всего что-то путаете. Объект уничтожается только после того, как будут уничтожены ВСЕ ссыли на него. Соответственно если у Вас по каким то причинам ссылки на объект buf уничтожаются раньше, то и получается что сам объект тоже умирает.
спустя 18 часов [обр] Kleo[досье]
Алексей, все оказалось несколко сложнее. Потратил несколько часов на то, чтобы разложить все по полочкам. Вот что получилось (придется немного углубиться в детали, но их не много и они просты):
В работе использую модуль WebOut Дмитрия Котерова. Он уже предоставляет объект, позволяющий рахватывать буфер вывода. При этом фактически в системе имеем два объекта WebOut (в дальнейшем буду просто называть этот объект буфером для упрощения). Первый из них создается автоматически при вызове конструкции use WebOut и служит для внутренних нужд самого модуля, второй же создается пользователем и
в дальнейшем работа и захват выходного потока идет именно через него. Выяснил, что уничтожается раньше чем надо объект второго буфера.
В примерах используются 4 файла которые позволяют наглядно протестировать проблему:
  1. Исполняемый файл - test.pl
  2. Модуль хранителя объекта - ObjReestr.pm
  3. Модуль обработки аjax запросов - Ajax.pm
  4. Модуль буфера - Buf.pm
спустя 3 минуты [обр] Kleo[досье]

Итак, исполняемый файл: test.pl:

#!/usr/bin/perl -wl

use strict;
use lib "C:/test/";

use ObjReestr; # объект реестра
use Ajax;      # объект обработки ajax-запросов
use Buf;       # объект захвата буфера (WebOut в реальной жизни)

# эмулируем создание первого буфера
my $buf = Buf->new(1);
# создаем хранитель объектов
my $objReestr = ObjReestr->new();

# Создаем и добавляем в хранитель объект ajax
# (Передаем в ajax первый буфер, чтобы проследить заодно и его состояние)
$objReestr->addObj(Ajax->new($buf), 'ajax');

Модуль хранителя ObjReestr.pm:

package ObjReestr;
use strict;

BEGIN { print 'ObjREESTR BEGIN';}

sub new
{
   my $class  = shift;
   return bless ({}, $class);
}

# Метод добавления объектов в реестр
# На вход подаются объект и его имя, под которым но будет храниться в реестре
sub addObj
{
   my ($self, $obj, $name) = @_;
   $self->{$name} = {'obj'=>$obj, 'package'=>ref $obj};
   return 1;
}

# Метод извлечения объекта из реестра по его имени
sub get
{
   my ($self, $name) = @_;
   return $self->{$name}{'obj'};
}

sub DESTROY
{
   my $self = shift;
   print 'ObjREESTR DESTROY';
}

1;

Модуль Ajax.pm

#!/usr/bin/perl -wl

package Ajax;

use strict;
BEGIN { print 'AJAX BEGIN';}

sub new
{
   my $class  = shift;
   my $self = bless ({}, $class);
   $self->{buf_1} = shift;       # запоминаем первый буфер
   $self->{buf_2} = Buf->new(2); # создаем рабочий буфер
   return $self;
}

sub DESTROY
{
   my $self = shift;
   print 'AJAX DESTROY:';
  # Состояние буферов на момент уничтожения объекта ajax.
  # Теоретически должны быть оба рабочих, т.к. объект ajax содержит ссылки на каждого их них:
   print "    AJAX BUF [index 1]: ". (ref $self->{buf_1} ? 'ok' : 'empty');
   print "    AJAX BUF [index 2]: ". (ref $self->{buf_2} ? 'ok' : 'empty');
}

1;

Модуль буфера Buf.pm (здесь все просто и без комментариев):

#!/usr/bin/perl -wl

package Buf;
use strict;

BEGIN { print 'BUF BEGIN';}

sub new
{
   my $class  = shift;
   return bless ({'index'=>shift}, $class);
}

sub DESTROY
{
   my $self = shift;
   print 'BUF index:'.$self->{index}.' DESTROY';
}

1;
спустя 2 минуты [обр] Kleo[досье]

Запускаем испоняемый файл (perl test.pl), получаем результат:

ObjREESTR BEGIN
AJAX BEGIN
BUF BEGIN
ObjREESTR DESTROY
AJAX DESTROY:
    AJAX BUF [index 1]: ok
    AJAX BUF [index 2]: ok
BUF index:2 DESTROY
BUF index:1 DESTROY

Видим, что оба буфера у нас присутствуют.
Теперь не рабочий вариант: необходимо сделать так, чтобы объект имел возможность обратиться к реестру
(например понадобилась ему информация о статусе авторизации пользователя, которая хранится в одном из объектов в реестре). Как вариант можно передать на вход конструктора объекта сам объект хранителя и сохранить его в виде параметра, но в этом случае возникнет проблема с циклическими ссылками если сам он будет находиться в реестре.

спустя 4 минуты [обр] Kleo[досье]

Поэтому пробую делать так: пишу в модуле хранителя (ObjReestr.pm) следующий код:

sub createCallerReestr
{
   my $self     = shift;
   my $package  = ref $_[0];

   my $slot = $package."::objReestr";
   no strict "refs";
   *{$slot} = sub {$self->get(shift);};
   return 1;
}

а в исполняемом файле (test.pl) добавляю строчку:

$objReestr->createCallerReestr($objReestr->get('ajax'));

Таким образом получаю в объекте ajax метод objReestr, передав на вход
которого имя интересующего меня объекта, получаю этот объект в свое распоряжение.

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

ObjREESTR BEGIN
AJAX BEGIN
BUF BEGIN
BUF index:2 DESTROY
AJAX DESTROY:
    AJAX BUF [index 1]: ok
    AJAX BUF [index 2]: empty
BUF index:1 DESTROY
ObjREESTR DESTROY

Вот этот момент для меня остался не ясен. Теоретически ссылка на объект по прежнему находится в ajax но не смотря на это второй буфер "улетел".
К сожалению не нашел как можно отследить работу счетчика ссылок, возможно это бы что-то прояснило. Посему прошу вашей помощи в разъяснении.

спустя 1 час 51 минуту [обр] Kleo[досье]

Прошу прощенья за неточность, выкидывал лишний код чтобы не загромождать топик и выкинул первый shift в примере createCallerReestr. И исправлением метод выглядит так (вместо shift вставить $_[1]):

sub createCallerReestr
{
   my $self     = shift;
   my $package  = ref $_[0];

   my $slot = $package."::objReestr";
   no strict "refs";
   *{$slot} = sub {$self->get($_[1]);};
   return 1;
}

Кстати, если в исполняемом файле поменять местами объявления пакетов с

use ObjReestr; # объект реестра
use Ajax;      # объект обработки ajax-запросов
use Buf;       # объект захвата буфера (WebOut в реальной жизни)

на

use Buf;       # объект захвата буфера (WebOut в реальной жизни)
use Ajax;      # объект обработки ajax-запросов
use ObjReestr; # объект реестра

то все работает:

BUF BEGIN
AJAX BEGIN
ObjREESTR BEGIN
ObjREESTR DESTROY
AJAX DESTROY:
    AJAX BUF [index 1]: ok
    AJAX BUF [index 2]: ok
BUF index:1 DESTROY
BUF index:2 DESTROY

Такое ощущение, что счетчик ссылок задействован не полностью или где-то перекрывается. Не могу связать порядок уничтожения объектов и метод createCallerReestr. А дело, видимо, именно в нем. Есть мысли по этому поводу?

спустя 13 минут [обр] Алексей Севрюков(198/1280)[досье]
сообщение промодерировано
Данный модуль очень хитрый и понять его достаточно сложно. В связи с этим рекомендую обратиться на сайт разработчика по адресу: http://forum.dklab.ru/search.html?q=WebOut&sort_by=5&nospam=GEN_BY_JS
Предполагаю что там Вы найдете ответ быстрее, а если не найдете, то, возможно, сам автор поможет Вам разобраться. Для экономии времени создайте тему на том форуме и дайте ссылку на эту тему.
спустя 18 минут [обр] Kleo[досье]

Алексей, снова спасибо :)
Модуль разобрал досконально, изучил каждую строку. По большому счету там ничего сложного нет. Да и дело не в нем, я ведь специально создал тестовые модули, готовые к использованию. Там этот модуль вообще не упоминается и не учавствует в работе. Все упрощено до рабочего минимума и на выходе получаю ту же самую ошибку что и в рабочем проекте. Проблема, как я выяснил в методе createCallerReestr, он создает в указанном пакете метод доступа к себе любимому:

sub createCallerReestr
{
   my $self     = shift;
   my $package  = ref $_[0];

   my $slot = $package."::objReestr";
   no strict "refs";
   *{$slot} = sub {$self->get($_[1]);};
   return 1;
}

и вот именно он каким-то образом влияет на счетчик ссылок. А как и почему понять пока не могу.

спустя 13 минут [обр] Алексей Севрюков(198/1280)[досье]

Kleo[досье] Я под вечер уже плохо соображаю:

sub createCallerReestr
{
   my $self     = shift;
   my $package  = ref $_[0];

   {
     no strict "refs";
     my $obj=$_[0];
     my $p=$_[1];
     *{$package."::objReestr"} = sub {$obj->get($p);};
   }
   return 1;
}

Да и сам метод как то не красиво создается, думается мне что есть другие пути. Завтра если будет время почитаю доки, думаю что ответ есть где-нибудь в perldoc perlmod.

спустя 15 часов [обр] Kleo[досье]

Алексей, если метод написать таким образом, то при следующем порядке инициализации модулей в исполняемом файле:

use Buf;       # объект захвата буфера (WebOut в реальной жизни)
use Ajax;      # объект обработки ajax-запросов
use ObjReestr; # объект реестра

код получается рабочий, но стоит лишь изменить порядок инициализации, к примеру поставим модуль Ajax последним:

use Buf;       # объект захвата буфера (WebOut в реальной жизни)
use ObjReestr; # объект реестра
use Ajax;      # объект обработки ajax-запросов

снова получаем ошибку:

BUF BEGIN
ObjREESTR BEGIN
AJAX BEGIN
ObjREESTR DESTROY
BUF index:2 DESTROY
AJAX DESTROY:
    AJAX BUF [index 1]: ok
    AJAX BUF [index 2]: empty
BUF index:1 DESTROY

объект буфера с индексом 2 не дожил до светлого будущего. Любопытная ситуация :) Никак не могу докопаться до сути

Powered by POEM™ Engine Copyright © 2002-2005