LINUX.ORG.RU

[C++][Haskell][Ocaml][Java][C#][?] позволяет ли ваш язык создать полноценный прокси для объектов?

 , , ,


0

0

Рассмотрим "полноценные" в смысле смоллтолка объекты, т.е. те, в которых можно вызывать виртуальные методы.

Для таких объектов надо написать функцию f, которая на входе получает любой полноценный объект класса К, а на выходе возвращает тоже полноценный объект класса К1, причем К1 является потомком класса К и имеет все приватные, защищенные и открытые поля и методы класса К, и еще одно открытое поле -- счетчик. Вы расслабились? рано! Каждый вызов метода объекта класса К1 должен сначала инкрементировать то самое новое открытое поле-счетчик, а затем уже вызывать соответствующий метод своего родителя.

1. Порутчикам Ржевским со своими динамическими языками -- молчать. Я и так знаю, что вы скажете.

2. Почему рассматриваем только полноценные объекты -- если объект не полноценный, то при путешествии объекта класса К1 он быстро станет рассмативаться как объект класса К и перестанет инкрементировать счетчик (то, что в плюсах возможны "неполноценные" объекты я не собираюсь обсуждать -- это уведет далеко).

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

4. На плюсах я сейчас вижу только "грязный" способ реализации -- использовать прямой доступ к vtbl на запись. Может кто предложит получше?

5. Дополненительное усложение: перед своим уничтожением объект должен записать счетчик в лог.


hm. мне почему то кажется, что по крайней мере в плюсах такого не получится. тебе ж поди это ещё и в рантайме нужно для произвольного класса?

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от Absurd

> На жабе можно сгенерить .class файл и загрузить его класслодером.

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

www_linux_org_ru ★★★★★
() автор топика

Рефлексия и генерация кода во время исполнения решит все проблемы. Реализуется как минимум на Си/Си++/Яве.

tailgunner ★★★★★
()
Ответ на: комментарий от klalafuda

> hm. мне почему то кажется, что по крайней мере в плюсах такого не получится. тебе ж поди это ещё и в рантайме нужно для произвольного класса?

( мне это не нужно, я пофлеймить вышел :-) но пример видимо таки напишу )

для произвольного класса с вирт. функциями -- вполне реально ИМХО.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от tailgunner

> Рефлексия и генерация кода во время исполнения решит все проблемы. Реализуется как минимум на Си/Си++/Яве.

На плюсах похоже можно без рефлексии и точно -- без генерации кода в рантайме.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от klalafuda

> можно пример?

завтра-послезавтра напишу сюда. сегодня совсем уже устал.

www_linux_org_ru ★★★★★
() автор топика

Нихрена не понял. Ты собираешься динамически создавать _класс_? Если да, то в языке без концепции метаклассов это не сделаешь. Только путем грязных хаков. Если нет, то какие проблемы? Включай объект класса K в объект класса K1 и переделывай соответствующие методы. Только право на самостоятельную смерть объект класса K иметь уже не будет. Другого способа, особенно если ты хочешь чтобы объекты K и K1 были между собой связаны в "традиционном" ООП-е нет.

В принципе, такая проблема должна хорошо решаться в рамках CLOS...

Macil ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

> На плюсах похоже можно без рефлексии

Вот когда покажешь, тогда и будет можно.

tailgunner ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

>> На жабе можно сгенерить .class файл и загрузить его класслодером.

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

Коды виртуальной машины известны. Если нет проблем с лицензией, то можно использовать компилятор который написан на яве и лежит в отдельном jar файле в jdk. Зависимостей у компилятора нет за исключением собственно jre. Для генерации класса-прокси надо просканировать проксируемый класс при помощи рефлексии. Мой коллега такое делал.

Absurd ★★★
()
Ответ на: комментарий от Macil

> Ты собираешься динамически создавать _класс_?

Да, именно так, причем на ужасном статическом С++ :-) как сделать это хаком, я придумал, вот думаю сейчас, как без хака...

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от tailgunner

> Рефлексия и генерация кода во время исполнения решит все проблемы. Реализуется как минимум на Си/Си++/Яве.

ты имеешь ввиду генерацию машинного кода? если да, то я бы не сказал, что 'это на c++'. это скорее какой-то грязный хак :-/

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

> ты имеешь ввиду генерацию машинного кода?

Можно просто текста на Си++ :)

> я бы не сказал, что 'это на c++'. это скорее какой-то грязный хак :-/

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

tailgunner ★★★★★
()
Ответ на: комментарий от Absurd

> Для генерации класса-прокси надо просканировать проксируемый класс при помощи рефлексии.

С возможностью просканировать никаких споров нет.

А вот возможность создать новый класс -- это вопрос. Т.е. я не знаю языковых механизмов явы, позволяющих создать такое. Только использование комманд виртуальной машины.

______________________

Да, в условии я не написал -- желательно сгенерить весь код на этапе компиляции, и в рантайме *ничего* не генерить.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от tailgunner

> ты имеешь ввиду генерацию машинного кода?

> Можно просто текста на Си++ :)

не-не-не. можно обойтись и без того, и без другого.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от www_linux_org_ru

>> Ты собираешься динамически создавать _класс_?

>Да, именно так, причем на ужасном статическом С++

Может быть глянуть в сторону LLVM?

Macil ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

> Да, в условии я не написал -- желательно сгенерить весь код на этапе компиляции, и в рантайме *ничего* не генерить.

вот и я о том же. иначе, это будет уже нечестно.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от tailgunner

> Можно просто текста на Си++ :)

для с++ сразу ставим условие -- имеются классы с ЗАКРЫТЫМ исходником, и для них f тоже должна работать.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от www_linux_org_ru

>как сделать это хаком, я придумал, вот думаю сейчас, как без хака...

Дык может это лучше через шаблоны а-ля паттерн Strategy (Александреску взрывает моск). Типа если нам нужен некий счетчик, создаем внутре целевого объекта еще один объект, тип которого задаем через С++шный шаблон, а в каждом методе целевого класса ставим вызов методов шаблонного класса.

Macil ★★★★★
()
Ответ на: комментарий от Macil

> Только право на самостоятельную смерть объект класса K иметь уже не будет.

Он должен иметь право самостоятельно умирать, только перед этим должен записать счетчик в лог.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от Macil

> Дык может это лучше через шаблоны а-ля паттерн Strategy (Александреску взрывает моск). Типа если нам нужен некий счетчик, создаем внутре целевого объекта еще один объект, тип которого задаем через С++шный шаблон, а в каждом методе целевого класса ставим вызов методов шаблонного класса.

AFAIU это не то. это просто. если я правильно понял условие, то есть базовый класс A и унаследованный от него B. ни в А ни в B никакого счетчика нет и добавлять руками в декларацию B мы его не можем, а тем более модифицировать поведение B. иначе, если можем, задача теряет всю соль.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от www_linux_org_ru

>А вот возможность создать новый класс -- это вопрос. Т.е. я не знаю языковых механизмов явы, позволяющих создать такое. Только использование комманд виртуальной машины.

java.lang.reflect.Proxy. Только оно создает класс на основе списка наследуемых интерфейсов, класса-предка ему задать нельзя.

>Да, в условии я не написал -- желательно сгенерить весь код на этапе компиляции, и в рантайме *ничего* не генерить.

В D наверно можно воспользоваться миксином и compile-time рефлексией. В С++ даже compile-time проверка существования метода "void foo()" у класса "Bar" требует трехэтажного SFINEA-шаблона с задействованием буста. А задачу генерации класса из списка типов полей Александреску смог решить только с помощью наследования от каждого из типов полей (Loki::GenScatterHierarchy<>, Loki::GenLinearHierarchy<>).

Absurd ★★★
()
Ответ на: комментарий от Absurd

> В D наверно можно воспользоваться миксином и compile-time рефлексией. В С++ даже compile-time проверка существования метода "void foo()" у класса "Bar" требует трехэтажного SFINEA-шаблона с задействованием буста. А задачу генерации класса из списка типов полей Александреску смог решить только с помощью наследования от каждого из типов полей (Loki::GenScatterHierarchy<>, Loki::GenLinearHierarchy<>).

ужас... как упражнения для [больноо] мозга - потянет. как практика... не приведи господь увидеть такое творение в живую :-/ уж на что я в принципе положительно отношусь к плюсам, но в указанном случае могу лишь выдать что-нить a'la 'Use Ruby Luke'.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от www_linux_org_ru

>> Только право на самостоятельную смерть объект класса K иметь уже не будет.

>Он должен иметь право самостоятельно умирать, только перед этим должен записать счетчик в лог.

Т.е. объект класса К (базовый объект) еще должен знать, что у него есть прокси (объект класса K1)?

Macil ★★★★★
()
Ответ на: комментарий от klalafuda

>AFAIU это не то.

Ну да, условиям это противоречит. Но эффект дает тот же, дополнительно решая кучу проблем с жизненным циклом, количеством экземпляров прокси, исключениями. Главная проблема в том, что сложновато будет реализовать динамическую природу. По идее, чтобы получить/убрать возможность использовать счетчик, объект придется клонировать. Но поскольку клонирование идет практически само в себя, больщих проблем быть не должно. Плюс открывается возможность использовать один и тот же объект-счетчик в объектах разных классов, т.е. сделать их жизненные циклы полностью независимыми.

Здесь еще важно, кто отвечает за жизненный цикл. Если объект создается фабрикой, то это одно, если нет - совсем другое.

Macil ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

> пропатчить указатель на vtbl гораздо проще

если я буду патчить потроха рантайма, то я смогу сделать хоть черта в ступе. но это 1) явное читерство 2) бред, т.к. см. п.1

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от Macil

> Т.е. объект класса К (базовый объект) еще должен знать, что у него есть прокси (объект класса K1)?

Пусть у нас есть g(K* k), причем исходников g у нас нет. Тогда мы имеем право подсунуть ей указатель на К1, т.е. вызвать g(f(k)). Внутри g объект k1==f(k) какое-то время пропутешествует, на нем отработают K-шные методы, потом он там умрет и запишет счетчик в лог.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от klalafuda

> но в указанном случае могу лишь выдать что-нить a'la 'Use Ruby Luke'.

Добрый ты... другой бы выдал волчий билет с занесением в лицевую панель :)

tailgunner ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

на самом деле прототип f должен быть такой: K1* f(K*)

а вовсе не K1 f(K) как я писал в условии.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от www_linux_org_ru

>Пусть у нас есть g(K* k), причем исходников g у нас нет.

Да, но что будет, если кто-то вдруг вызовет деструктор объекта k? Одно дело, если ты подсовываешь только k1. А что делать, если ты не имеешь контроля над жизненным циклом k?

Macil ★★★★★
()
Ответ на: комментарий от Macil

> ЗЫ: А вообще, ну его в топку! Убей в себе ООП! Даешь моделирование и DSL'ы!

А вот нифига. Это я так изучаю границы применимости плюсов и как их красиво расширить.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от Macil

> Да, но что будет, если кто-то вдруг вызовет деструктор объекта k?

Надо вначале проверять, что деструктор у К виртуальный, а раз виртуальный, то будет вызываться деструктор K1.

Кстати: если это не так, то желательно на этапе компиляции сказать "я отказывась работать, у вас класс К плохой!"

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от www_linux_org_ru

>Это я так изучаю границы применимости плюсов и как их красиво расширить.

Типа реальные пацаны красиво расширяют границы С++ с помощью LLVM. :))

Macil ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

> А вот нифига. Это я так изучаю границы применимости плюсов и как их красиво расширить.

когда то давно я тоже занимался расширением границ сознания. правда, то было желание компилировать паскалевский код под 32х бытный DOS4G. и ведь почти расширил. взял дельфю, перешерстил её RTL с Win32 на DOS и ещё куча прчих веществ. почти что заработало. так хотелось поиметь полноценный компилятор+обвязку с поддержкой 32х битного защищённого режима. слава богу, что вовремя отпустило.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

> если я буду патчить потроха рантайма, то я смогу сделать хоть черта в ступе. но это 1) явное читерство 2) бред, т.к. см. п.1

Сделай, только чтобы оно было короче и понятнее, чем на яве (а потом можно будет уже начать флейм с динамическими языками).

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от www_linux_org_ru

> Сделай, только чтобы оно было короче и понятнее, чем на яве (а потом можно будет уже начать флейм с динамическими языками).

зачем :-? под задачу и инструмент. мне лично кажется, что те же с++ тут явно не под задачу. разве что хочется внедриться в существующий объектник и понарасставлять там, скажем, брекпоинты счетчики или ещё какую гадость. но для этого есть другие средства.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от www_linux_org_ru

>а раз виртуальный, то будет вызываться деструктор K1.

Не будет. Хотя это проблема чисто теоретическая. У нас есть _объект_ k. Для "вкусноты" будем считать, что контроля за ЖЦ мы не имеем, т.е. его неконятно кто и главное когда создает, и непонятно кто и главное когда уничтожает.

Мы делаем объект-прокси k1 типа K1, который является потомком K. Объект k1 перенаправляет сообщения объекту k, по крайней мере это традиционное определение прокси. Как только кто-то пытается уничтожить k, с k1 начинаются большие проблемы.

Вроде это решается с помощью "умных" указателей.

Macil ★★★★★
()
Ответ на: комментарий от klalafuda

> слава богу, что вовремя отпустило.

я свою задачку оцениваю в 50 строк. ну если всякий лишний сервис добавить, то в 100. а иначе язык надо признать непригодным для нее.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от tailgunner

>>но в указанном случае могу лишь выдать что-нить a'la 'Use Ruby Luke'.

>Добрый ты... другой бы выдал волчий билет с занесением в лицевую панель :)

Типо извращаться никогда не надо? А вот хрен - надо. Иногда. Для тестирования хреново написанного модуля с тучей зависимостей в изолированном окружении например.

Absurd ★★★
()
Ответ на: комментарий от Macil

> Типа реальные пацаны красиво расширяют границы С++ с помощью LLVM. :))

А над этим стоит подумать.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от Macil

> Типа реальные пацаны красиво расширяют границы С++ с помощью LLVM. :))

А над этим стоит подумать. Подумать не в качестве способа решения данной задачи, а в качестве способа расширить свое сознание :-)

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от Macil

> Например hibernate что-то подобное делает.

Кстати да. Можно подумать, как следует сформулировать эту задачку в следущий раз... но для собственно hibernate-а вроде как достаточно умных указателей.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от Absurd

>> Добрый ты... другой бы выдал волчий билет с занесением в лицевую панель :)

> Типо извращаться никогда не надо? А вот хрен - надо.

"Не держись устава яко слепой за стенку" (c). Любые правила можно нарушить, если готов к последствиям.

tailgunner ★★★★★
()

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

Олег Киселёв сделал более чем полноценную реализацию объектной системы на Хаскеле. Там это, по моим воспоминаниям, вполне возможно. Собственно, ЕМНИМЭ, каждый объект получается там как расширение другого объекта одним полем (кроме пустых объектов, естественно), так что тип объекта получается по несложным правилам. Тип K1 получается примерно по таким же правилам. это, в свою очередь, означает, что можно соорудить тайпкласс, делающий, в общем, то, что ты и хочешь.

Другой вопрос, что я этой объектной системой реально не пользовался, только читал, причём давно. Так что полноценной реализации не будет.

Miguel ★★★★★
()


в общем, я реквистирую живой пример решения задачи на C++. потому что лично мне кажется, что я все-таки не понял, чего же на самом деле хочется :-/

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от Miguel

1. У тебя в блоге прекрасная флеймообразующая тема насчет template virtual. Предлагаю запостить ее на ЛОР, только приведи несколько примеров на плюсах, КАК ты этот шаблон собираешься использовать. Твои примеры на хаскеле мне ничего не говорят о желаемом использовании, видимо они слишком абстрактны.

2. Насчет системы Олега Киселева -- кинь ссылку.

Возникают вопросы -- а для чего она делалась, что-то неудобно делать напрямую через тайпклассы?

3. Для хаскеля мою задачу можно переформулировать. Допустим у нас есть тайпклассы с понятным наследованием Animal Mammal Cat Bird, и хочется чтобы

А. готовый код, работающий с Животным, так же умел работать с Котом

Б. при вызове методов Животного, которое на самом деле Кот, оно оставалось Котом

В. ну собственно f -- придумай что-нить в этом духе, что попроще делать -- я не знаю, как она записывается:

-- если f на входе получает объект c типом, являющимся инстансом хорошего тайпкласса, то на выходе она отдает объект с типом, являющимся инстансом этого же тайпкласса и одновременно инстансом тайпкласса CountedInvocations

-- определять "хороший ли тайпкласс" должна сама f, если плохой -- ругаться во время компиляции

Как минимум, я хочу знать, достаточно ли для этого Хаскеля/Template Haskell/еще-чего-то. И правильно ли я описал задачу, может она должна формулировать по-другому.

www_linux_org_ru ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.