LINUX.ORG.RU

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

 , , ,


0

0

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

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

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

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

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

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

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

Ответ на: комментарий от klalafuda

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

щас позавтракаю и наваяю

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

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

Sorry, пример откладывается, так как в процессе написания до меня сейчас дошло, что можно еще и -> перегрузить. Но с другой стороны, хранить внутри себя указатель на проксируемый объект не хочетcя (по причинам, указанным Macil). Видимо, примеров будет несколько...

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

> А можно уже на динамическом языке показать? CLOS, например

Хочешь -- запости (но меня больше интересует как это будет на языках со статикой)

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

На данный момент моего владения плюсами не хватает для этой задачи.

Впрочем, она и затевалась для нахождения границ и обнаружения всех неудобств плюсов. Я ее еще порешаю недельку, так что не ждите от меня скорого варианта решения.

С другой стороны, я буду читать что здесь напишут.

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

Мне тут приходила в голову идея сделать класс "copy_on_write", т.е. обёртку, которая при копировании ведёт себя как shared_ptr, но при вызове любого не const метода автоматически "открепляется". Но за реализацию я не взялся пока.

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

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

Выкрою время - сделаю.

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

http://homepages.cwi.nl/~ralf/OOHaskell/

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

Олег - такой могутный мужик, которому сделать полноценную реализацию ООП просто для затыкание тех, кто орёт, что это невозможно - как два байта переслать.

> И правильно ли я описал задачу

Не знаю, эту формулировку я не понял.

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

> Мне тут приходила в голову идея сделать класс "copy_on_write", т.е.
> обёртку, которая при копировании ведёт себя как shared_ptr, но при
> вызове любого не const метода автоматически "открепляется". Но за
> реализацию я не взялся пока.

struct foo {
    void a() const {}
    void b() {}
};

boost::shared_ptr<foo> f(new foo);
f->a();
f->b();

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

// wbr

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

>ммм... и куда что будет открепляться в последнем вызове :-?

Ничего никуда не будет, т.к. ссылка на объект всего одна.

Предполагаемый пример использования:

typedef copy_on_write_ptr < std::vector<float> > myvector;

myvector a(new std::vector<float>());

a->resize(100000);

fill(a);

myvector b = a; //копирования вектора не происходит, потребление памяти sizeof(float)*100000+копейки

assert((*a==*b) && (&(*a[0])==&(*b[0]));//и т.д.

cout << b.size() << endl; //константный метод, нет копирования

b.puch_back(3.1415); //не константный метод, копирование! на короткий момент выполняется assert((*a==*b) && (&(*a[0])!=&(*b[0])) и затем для новой копии вызывается собственно puch_back. потребление памяти sizeof(float)*100000+100001+копейки

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

последнюю строку читать как

b->puch_back(3.1415); //не константный метод, копирование! на короткий момент выполняется assert((*a==*b) && (&(*a[0])!=&(*b[0])) и затем для новой копии вызывается собственно puch_back. потребление памяти sizeof(float)*(100000+100001)+копейки

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

Всякое бывает. Особенно, когда начинается оптимизация.

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

> Мне тут приходила в голову идея сделать класс "copy_on_write", т.е. обёртку, которая при копировании ведёт себя как shared_ptr, но при вызове любого не const метода автоматически "открепляется". Но за реализацию я не взялся пока.

Мне нравится эта идея. И поэтому кажется, что кто-нить такое уже сделал.

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

Что же касается изначальной моей задачи -- после осознания поста Macil-а на тему "это все надо делать через LLVM" у меня пропал интерес ковырять vtable, так как результат будет убог (только виртуальные функции), а заморочек по компилятор-независимому типобезопасному ковырянию -- очень много.

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

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

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

Собственно я хотел вот это услышать: all the conventional OO features plus more advanced ones, including first-class lexically scoped classes, implicitly polymorphic classes, flexible multiple inheritance, safe downcasts and safe co-variant arguments.

Ладно, почитаем.

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

> b->puch_back(3.1415); //не константный метод, копирование! на короткий момент выполняется assert((*a==*b) && (&(*a[0])!=&(*b[0])) и затем для новой копии вызывается собственно puch_back. потребление памяти sizeof(float)*(100000+100001)+копейки

мне почему-то кажется, что std::basic_string так сделана, нет? уверен, что в QString есть что-то подобное. точнее, с существенно большей грануляцией. и конечно же не забываем про thread safety :)

// wbr

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

> мне почему-то кажется, что std::basic_string так сделана, нет? и конечно же не забываем про thread safety :)

вроде как строки из стандарта как раз *не* нитебезопасные?

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

> вроде как строки из стандарта как раз *не* нитебезопасные?

stl-ные - нет. а вот QString AFAIR да.

// wbr

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

>мне почему-то кажется, что std::basic_string так сделана, нет?

Некоторые реализации. GNU C++, например. Но если удастся делать тоже для любого класса — это замечательно. Во всяком случае пока нельзя пользоваться move semantics.

legolegs ★★★★★
()

Если я правильно понял, то такая функциональность нужна:
(defclass k ()
  ((x :initform 0 :initarg :x :accessor x)
   (y :initform 0 :initarg :y :accessor y)))

(defclass k1 (k)
  ((counter :initform 0 :accessor counter)))

;;Методы :before в CLOS вызываются перед основными методами, принимают те же аргументы
(defmethod x :before ((instance k1))
  (incf (counter instance)))

(defmethod (setf x) :before (new (instance k1))
  (declare (ignore new))
  (incf (counter instance)))

(defmethod y :before ((instance k1))
  (incf (counter instance)))

(defmethod (setf y) :before (new (instance k1))
  (declare (ignore new))
  (incf (counter instance)))

(defparameter k-instance (make-instance 'k :x 5 :y 10))

(change-class k-instance 'k1)

(print (x k-instance)) ;; => 5
(print (y k-instance)) ;; => 10
(setf (x k-instance) 50)
(print (x k-instance)) ;; => 50

(print (counter k-instance)) ;; => 4

Или еще и автоматически? Т.е. не вручную добавлять обертки? В лиспе макросом можно, или через MOP.

guest-3484-2009
()
Ответ на: комментарий от guest-3484-2009

В случае с автоматизированным добалением - пишем обертку над defclass, которая создает еще один класс, наследник, со счетчиком, и сохраняет имя в некоторую таблицу, во время компиляции. Кстати, пример использования eval-when. И обертку над defmethod, которая парсит аргументы, кроме основного метода создает соответствующий :before, в котором имена отслеживаемых классов заменяет на соответствующие "k1".

И функцию trace-instance, которая делает change-class на объекте на соответствующий "k1". Опционально - untrace-instance, которая меняет класс на первоначальный и возвращает число в слоте-счетчике.

guest-3484-2009
()
Ответ на: комментарий от www_linux_org_ru

>> А можно уже на динамическом языке показать? CLOS, например 

> Хочешь -- запости

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

def create_proxy_class(klass)
  new_class_name = "#{klass}1"
  eval %Q{
    class #{new_class_name} < klass
      attr_reader :counter
      private; def inc_counter; @counter = 0 if @counter.nil?
        @counter+=1
      end
    end
  }
 
  (klass.instance_methods - klass.superclass.instance_methods).each {|mname|
    eval %Q{
      class #{new_class_name}
        def #{mname}(*args); inc_counter; super(*args); end
      end
    }
  }
  
  eval %Q{ #{new_class_name} }
end

Этот метод берет класс K и возвращает класс K1, который является потомком К. 
Пример:

tString1 = create_proxy_class(String)
s1 = tString1.new("Hello")
puts s1.class
s1.length
s1.reverse!
puts s1.counter

Результат:

String1
2

smh ★★★
()

Напишите уже хоть один пример на С++. Потому что здесь как минимум две проблемы - рефлексия, вместо которой в С++ вы ничего не придумаете, вы даже не узнаете количество публичных методов у объекта, который вам передадут, - и еще надо в рантайме создать класс и затем объект указанного класса. Ради Бога, не пытайтесь менять vtbl, то что у вас получится, будет даже для того компилятора, для которого вы это напишете, неверно с вероятностью 99%.

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

> Напишите уже хоть один пример на С++.

По просьбам трудящихся... cм. следующий пост.

> Ради Бога, не пытайтесь менять vtbl, то что у вас получится, будет даже для того компилятора, для которого вы это напишете, неверно с вероятностью 99%.

И где оно сломается?

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

> в общем, я реквистирую живой пример решения задачи на C++

$ g++ -g  apply-test17.cxx && ./a.out 
 ++usage=1
123
 ++usage=2
1.5


Например, так:


#include <iostream>

#define p(x) std::cout << " "#x "=" << x << '\n';

typedef void (*FPointer)(...);

#define VTABLE (*(FPointer**)this)

/// я умею форвардить только функции отдающие void, но вроде как возможно любые

#define FORWARD_TO(vtable_offset) (                     \
  __builtin_return(                                     \
    __builtin_apply(                                    \
      VTABLE[vtable_offset],                            \
      __builtin_apply_args(),                           \
      100                                               \
    )                                                   \
  )                                                     \
)
template<class T> struct Counted: public T {
  static const int parent_vf_count=2; /// 2 тоже можно посчитать, но лень
  Counted(T& c): T(c), usage(0) {
    /// махинации с vtable -- сдвигаем ее начало
    /// typeinfo перестанет работать, кстати
    FPointer** tmp = (FPointer**)this; (*tmp)+=parent_vf_count;
  }
  int usage;
  private:
    /// boilerplate code... не пишу под это макрос, так как так нагляднее
    virtual void apply0(...) { p(++usage); FORWARD_TO(0-parent_vf_count); }
    virtual void apply1(...) { p(++usage); FORWARD_TO(1-parent_vf_count); }
    virtual void apply2(...) { p(++usage); FORWARD_TO(2-parent_vf_count); }
};
template<class T> Counted<T>* counted(T& t) {
  Counted<T>* res = new Counted<T>(t);
  return res;
}

/// //////////////////////////////////////////////////////////////// usage:

struct Cat {
  virtual void say(int i   ) { std::cout << i << '\n'; }
  virtual void say(double x) { std::cout << x << '\n'; }
};
int main()
{
  Cat c; Cat* cc=counted(c);
  cc->say(123);
  cc->say(1.5);
  return 0;
}

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

а) Какое количество методов apply() вы ни напишете в Counted<>, найдется класс, у которого виртуальных методов больше, так что для него Counted работать не будет. б) Это не будет работать с множественным наследованием.

На компиляторах, отличных от gcc, это не то что работать не будет - даже не скомпилируется.

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

> а) Какое количество методов apply() вы ни напишете в Counted<>, найдется класс, у которого виртуальных методов больше, так что для него Counted работать не будет.

быстрый фикс: поставить static_assert на количество виртуальных методов

> б) Это не будет работать с множественным наследованием.

Подумаю.

> в) на компиляторах, отличных от gcc, это не то что работать не будет - даже не скомпилируется.

У них можно определить эти __xxx_builtin по-своему, или даже написать свой макрос форвардинга.

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

> Какое количество методов apply() вы ни напишете в Counted<>, найдется класс, у которого виртуальных методов больше,

Там еще одно магическое число 100 -- вот его я вообще не знаю, как считать.

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