16:32 

Трюк 0x0008 — Never too late

Куб 0
В устоявшуюся терминологию программистов C++ входят понятие раннего связывания (на стадии компиляции) и понятие позднего, отложенного связывания (динамически, во время исполнения). Далее в статье будет предложено решение для не просто позднего, а по-настоящему запоздалого связывания данных. Представьте, что Вы можете выбирать обработчик для данных прямо во время выполнения программы и, более того, ассоциировать с данными несколько обработчиков. Представьте, что при этом работает полиморфизм. Представьте, что если Вам более не нужны выдаваемые объектом данные, Вы можете "отключиться" от объекта, как клиент от сервера.
Как такое реализовать?
Со времен C известна методика использования указателей на функции. Это так называемый "косвенный вызов", используемый для организации callback-функций. В C++ на смену косвенным вызовам пришел объектно-ориентированный подход, но, что важно, косвенные вызовы все так же доступны для использования. Что они могут дать в C++? Прежде всего, некоторое нарушение инкапсуляции для классов, которое в малых дозах может оказаться полезным.
Если условно назвать иерархию классов при наследовании "вертикальной", то использование косвенных вызовов позволяет получить "горизонтальную" иерархию.
Для организации позднего связывания задействованы два класса — Signal и Wire. Класс Signal используется как базовый для создания классов-сигналов. Класс-сигнал является связующим звеном между отправителем и получателем данных. Сигнал может генерироваться где угодно, и получат его все подключенные к сигналу клиенты. Класс Wire используется для увязывания сигнала с клиентом-получателем (метод Connect()), и для генерации сигнала из требуемого места программы (метод Callback()).
Для удобства использования основные вызовы объявлены через макросы.
Макрос DECLARE_SIGNAL объявляет новый класс-сигнал. Это объявление может быть сделано как в глобальном пространстве имен, так и внутри класса. Макросы CONNECT_SIGNAL и DISCONNECT_SIGNAL устанавливают или разрывают связь с клиентом; в качестве клиента выступает метод класса. Макрос CALLBACK эмитирует класс-сигнал.

//------------------------------------------------------------
#include <vector>
template <class ParType>
class Signal {
public:
typedef ParType slot_par;
typedef const ParType & slot_ref_par;
template <class ClassType>
class SubSignal {
public:
typedef void (ClassType::* slot_ptr)(slot_par);
typedef void (ClassType::* slot_ref_ptr)(slot_ref_par);
};
};

template <class SignalType>
class Wire {
public:
template <class ClassType>
static void Connect(ClassType *Receiver,
typename SignalType::SubSignal<ClassType>::slot_ptr Slot) {
m_Vect.push_back(new InternalWire<ClassType>(Receiver, Slot));
}
template <class ClassType>
static void Connect(ClassType *Receiver,
typename SignalType::SubSignal<ClassType>::slot_ref_ptr Slot) {
m_Vect.push_back(new InternalWire<ClassType>(Receiver, Slot));
}
template <class ClassType>
static void Disconnect(ClassType *Receiver,
typename SignalType::SubSignal<ClassType>::slot_ptr Slot) {
for (wire_vect::iterator It=m_Vect.begin(); It!=m_Vect.end(); It++) {
InternalWire<ClassType> *El=(InternalWire<ClassType> *)(*It);
if ((El->GetPtr()==Slot) && (El->GetReceiver()==Receiver)) {
delete El;
m_Vect.erase(It);
break;
}
}
}
template <class ClassType>
static void Disconnect(ClassType *Receiver,
typename SignalType::SubSignal<ClassType>::slot_ref_ptr Slot) {
for (wire_vect::iterator It=m_Vect.begin(); It!=m_Vect.end(); It++) {
InternalWire<ClassType> *El=(InternalWire<ClassType> *)(*It);
if ((El->GetRefPtr()==Slot) && (El->GetReceiver()==Receiver)) {
delete El;
m_Vect.erase(It);
break;
}
}
}
static void Callback(typename SignalType::slot_ref_par Par) {
for (size_t __I=0; __I<m_Vect.size(); __I++) {
m_Vect[__I]->Callback(Par);
}
}
class InternalWireBase {
public:
virtual void Callback(typename SignalType::slot_ref_par Par)=0;
};
template <class ClassType>
class InternalWire: public InternalWireBase {
public:
InternalWire(ClassType * Receiver,
typename SignalType::SubSignal<ClassType>::slot_ptr Ptr):
m_Receiver(Receiver), m_Ptr(Ptr), m_RefPtr(0) {}
InternalWire(ClassType * Receiver,
typename SignalType::SubSignal<ClassType>::slot_ref_ptr Ptr):
m_Receiver(Receiver), m_RefPtr(Ptr), m_Ptr(0) {}
void Callback(typename SignalType::slot_ref_par Par) {
if (m_Ptr) {
(m_Receiver->*m_Ptr)(Par);
}
if (m_RefPtr) {
(m_Receiver->*m_RefPtr)(Par);
}
}
typename SignalType::SubSignal<ClassType>::slot_ptr GetPtr() {
return m_Ptr;
}
typename SignalType::SubSignal<ClassType>::slot_ref_ptr GetRefPtr() {
return m_RefPtr;
}
ClassType * GetReceiver() { return m_Receiver; }
private:
typename SignalType::SubSignal<ClassType>::slot_ptr m_Ptr;
typename SignalType::SubSignal<ClassType>::slot_ref_ptr m_RefPtr;
ClassType * m_Receiver;
};
private:
typedef std::vector<InternalWireBase *> wire_vect;
static wire_vect m_Vect;
};
template <class SignalType>
typename Wire<SignalType>::wire_vect Wire<SignalType>::m_Vect;

#define DECLARE_SIGNAL(_SignalName, Type) class \
_SignalName: public Signal<Type> {};
#define CONNECT_SIGNAL(_SignalName, Class, Obj, _SlotName) Wire\
<_SignalName>::Connect<Class>(Obj, &Class::_SlotName);
#define DISCONNECT_SIGNAL(_SignalName, Class, Obj, _SlotName) Wire\
<_SignalName>::Disconnect<Class>(Obj, &Class::_SlotName);
#define CALLBACK(_SignalName, Value) Wire<_SignalName>::Callback(Value);
//------------------------------------------------------------
Далее приведен код примера.

//------------------------------------------------------------
#include <iostream>
#include <string>

class Test {
public:
virtual void print(const char *s) {
std::cout << s << std::endl; }
virtual void printInt(const int &Num) {
std::cout << Num << std::endl; }
};

class DerivedTest: public Test {
public:
void print(const char *s) {
std::cout << s << " (derived)" << std::endl; }
};


class A {
public:
DECLARE_SIGNAL(CtorSignal, const char *);
DECLARE_SIGNAL(DtorSignal, const char *);
DECLARE_SIGNAL(MthdSignal, const char *);
DECLARE_SIGNAL(UpdateSignal, int);

A() {
CALLBACK(CtorSignal, "constructor");
}
void Method(int Num) {
CALLBACK(UpdateSignal, Num);
CALLBACK(MthdSignal, "method");
}
~A() {
CALLBACK(DtorSignal, "destructor");
}
};

A *a;
DerivedTest dtst;
Test *p_Tst=&dtst;
Test tst;
int main() {
CONNECT_SIGNAL(A::CtorSignal, Test, p_Tst, print);
CONNECT_SIGNAL(A::DtorSignal, Test, p_Tst, print);
CONNECT_SIGNAL(A::MthdSignal, Test, &tst, print);

a=new A();
std::cout << "-------------------" << std::endl;
a->Method(0);
std::cout << "-------------------" << std::endl;
DISCONNECT_SIGNAL(A::MthdSignal, Test, &tst, print);
CONNECT_SIGNAL(A::MthdSignal, DerivedTest, &dtst, print);
CONNECT_SIGNAL(A::UpdateSignal, DerivedTest, &dtst, printInt);
a->Method(5);
std::cout << "-------------------" << std::endl;
delete a;

return 0;
}
//------------------------------------------------------------
Вот, что покажет на экране эта программа.

//------------------------------------------------------------
constructor (derived)
-------------------
method (base)
-------------------
value 5
method (derived)
-------------------
destructor (derived)
//------------------------------------------------------------



@темы: Нетривиальный вызов, Трюки с наследованием, Трюки с шаблонами, Философия

URL
Комментарии
2008-03-19 в 19:21 

Боглен
Партизанский дуэт
Оченно напоминает мне реализацию эвентов в .NET))

2008-03-20 в 01:00 

Куб 0
с дотнетом практики работы не имел. вожусь в основном с чистым С++ и парой библиотек.
а идея вышеизложенного навеяна системой сигналов-слотов в Qt. хотя в данной реализации это просто по-человечески оформленный механизм использования callback-функций, и ничего более того.
а статью надо бы дописать, да... а то нечитабельно и нифига непонятно.

URL
     

Записная книжка программиста C++

главная