Я работаю над объектом, который представляет нормальные значения данных, использующие функционально-реактивное программирование, которые изменяют свои значения при изменении зависимого значения. Я имею в виду, скажем, у вас есть var3 = var1 + var2; и затем при изменении значения var1 значение var3 обновляется автоматически. В С ++ это сложно, но с помощью функции обновления, вызываемой где-то в другом рабочем потоке, вы можете создать вид функционально реактивной.
Итак, вот мой метод для этого. Я создаю шаблонный объект с именем Reactive, который может иметь любой тип, и затем перегружаю его операторы, так что когда два из этих реактивов, скажем, суммируются, не только результирующее значение равно их сумме, но и лямбда Сделал, который хранит операцию в std :: function, которая может быть вызвана снова позже, чтобы обновить значение результирующего в любой момент, когда вы вызываете его функцию обновления.
Возникает пара вопросов. Что делать, если одно из зависимых значений разрушено. даже если результирующий Reactive все еще имеет то, что он считает действительной лямбда, аргументы, которые использовала лямбда, больше не существуют. Чтобы объяснить это, я поворачиваюсь к boost :: signal2, чтобы настроить систему сигналов и слотов, чтобы проинформировать результирующих о разрушении одного из их зависимых. Когда получатель получает сигнал, его аффективная функция обнуляется и не будет вызываться при обновлении.
Чтобы реактивы могли выполнять эту операцию +, должен быть создан временный реактив, который имеет собственный сигнал, а затем оператор = должен быть перегружен, чтобы переместить данные во временный результирующий реактив. Однако сигналы не могут быть скопированы. Я обошел это, поместив сигнал разрушения в std :: unique_ptr, используя std :: move, когда оператор = получил Reactive &&, Кризис предотвращен.
Теперь вот где я застрял. Позже я понял, что, несмотря на то, что конструкция перемещения была в порядке, все еще не было способа скопировать конструкцию одного из моих Reactives, потому что, скажем, var3 = var1 + var2; и затем вы делаете это: var4 = var3; Тогда каким-то образом сигналы разрушения в var1 и var2 нуждаются в способе сообщить var4, что они были уничтожены. Я наконец-то придумал, как настроить его так, чтобы у меня был подобъект с именем Proxy, который был функтором, который содержал boost :: signal2 :: signal, а затем у каждого объекта был бы объект, содержащийся в std :: shared_ptr , Если у Reactive есть ссылка на этот прокси-сервер, то он связывает свой метод извлечения информации с этим прокси-сервером. Затем заместитель прикрепляет туда сигналы к этому доверенному лицу. Когда прокси вызывается, он также вызывает все свои соединения. Это позволяет копиям получать сигналы разрушения от иждивенцев.
Проблема в том, что подключение прокси к зависимому сигналу требует, чтобы у прокси был конструктор копирования, или, по крайней мере, это ошибка, которую дает мне msvc. Очевидно, boost :: signal2 :: signal :: connect использует свой конструктор копирования, чего не может сделать, потому что прокси-сервер сам содержит сигнал. Я даю вам всю эту информацию, потому что я все еще не уверен, является ли это лучшим решением. Я выбрал сигналы и слоты, потому что я наиболее знаком с ними, но, если у вас есть лучшее решение, укажите это. В противном случае, пожалуйста, помогите мне избежать этой ошибки.
Кстати, Slot — это просто способ сделать функцию Unreact () функтором, уникальным для каждого Reactive.
Вот объект:
template<class T>
class Reactive
{
template<class H>
friend class Reactive;
class Slot : public boost::signals2::trackable
{
public:
Slot(std::function<void()> & func) :
m_Func(func)
{}
void operator()()
{m_Func();}
private:
std::function<void()> m_Func;
};
class Proxy : public boost::signals2::trackable
{
Proxy(const Proxy & s);
Proxy & operator=(const Proxy & s);
public:
Proxy(){}
void operator()()
{m_Informer();}
void attach(Slot & m_Unreacter)
{m_Informer.connect(m_Unreacter);}
private:
boost::signals2::signal<void()> m_Informer;
};
public:
~Reactive()
{
(*m_SendDestruct)();
}
Reactive() :
m_SendDestruct(new boost::signals2::signal<void()>),
m_Proxy(new Proxy),
m_ReceiveDestruct(std::function<void()>(std::bind(&Reactive::Unreact, this))),
m_Affecter(nullptr)
{
m_Proxy->attach(m_ReceiveDestruct);
}
template<class H>
Reactive(const H & data) :
m_SendDestruct(new boost::signals2::signal<void()>),
m_Proxy(new Proxy),
m_ReceiveDestruct(std::function<void()>(std::bind(&Reactive::Unreact, this))),
m_Affecter(nullptr),
m_Data(data)
{
m_Proxy->attach(m_ReceiveDestruct);
}
Reactive(const Reactive & reac) :
m_SendDestruct(new boost::signals2::signal<void()>),
m_Proxy(reac.m_Proxy),
m_ReceiveDestruct(std::function<void()>(std::bind(&Reactive::Unreact, this))),
m_Affecter(reac.m_Affecter),
m_Data(reac.m_Data)
{
m_Proxy->attach(m_ReceiveDestruct);
}
Reactive(Reactive && reac) :
m_SendDestruct(std::move(reac.m_SendDestruct)),
m_Proxy(reac.m_Proxy),
m_ReceiveDestruct(std::function<void()>(std::bind(&Reactive::Unreact, this))),
m_Affecter(reac.m_Affecter),
m_Data(reac.m_Data)
{
m_Proxy->attach(m_ReceiveDestruct);
}
Reactive & operator=(const T & data)
{
m_Data = data;
return *this;
}
Reactive & operator=(const Reactive & reac)
{
m_Proxy = reac.m_Proxy;
m_Proxy.attach(m_ReceiveDestruct);
m_Affecter = reac.m_Affecter;
m_Data = reac.m_Data;
}
Reactive & operator=(Reactive && reac)
{
m_SendDestruct = std::move(reac.m_SendDestruct);
m_Proxy = reac.m_Proxy;
m_Affecter(reac.m_Affecter);
m_Data(reac.m_Data);
}
template<class H>
Reactive & operator+(const H & rhs)
{
m_Data += rhs;
return *this;
}
template<class H>
auto operator+(Reactive<H> & rhs) -> Reactive<decltype(m_Data + rhs.m_Data)> &&
{
Reactive<decltype(m_Data + rhs.m_Data)> m_temp;
std::function<decltype(m_Data + rhs.m_Data)()> func;
if (!rhs.m_Affecter)
func = [&](){ return m_Data + rhs.m_Data;};
else
func = [&](){return m_Data + rhs.m_Affecter();};
m_SendDestruct->connect((*m_temp.m_Proxy));
rhs.m_SendDestruct->connect((*m_temp.m_Proxy));
return std::forward<Reactive<decltype(m_Data+rhs.m_Data)> &&>(m_temp);
}
template<class H>
Reactive && operator+(Reactive<H> && rhs)
{
Reactive && m_Temp
}
T & Get()
{
return m_Data;
}
void Update()
{
if(m_Affecter)
m_Data = m_Affecter();
}
void Unreact()
{
m_Affecter = nullptr;
(*m_SendDestruct)();
}
private:
std::unique_ptr<boost::signals2::signal<void()> > m_SendDestruct;
std::shared_ptr<Proxy> m_Proxy;
Slot m_ReceiveDestruct;
std::function<T()> m_Affecter;
T m_Data;
};
и простой тест
int main()
{
Reactive<int> vel(10);
Reactive<int> acc(5);
Reactive<int> time(5);
Reactive<int> result = vel + acc + time;
system("PAUSE");
return 0;
}
и вот предупреждения / ошибки:
1>main.cpp(86): warning C4355: 'this' : used in base member initializer list
1> main.cpp(83) : while compiling class template member function 'Reactive<T>::Reactive(Reactive<T> &&)'
1> with
1> [
1> T=int
1> ]
1> main.cpp(174) : see reference to class template instantiation 'Reactive<T>' being compiled
1> with
1> [
1> T=int
1> ]
1>main.cpp(66): warning C4355: 'this' : used in base member initializer list
1> main.cpp(174) : see reference to function template instantiation 'Reactive<T>::Reactive<int>(const H &)' being compiled
1> with
1> [
1> T=int,
1> H=int
1> ]
1>main.cpp(56): warning C4355: 'this' : used in base member initializer list
1> main.cpp(53) : while compiling class template member function 'Reactive<T>::Reactive(void)'
1> with
1> [
1> T=int
1> ]
1>c:\program files (x86)\boost\boost_1_53_0\boost\signals2\detail\slot_template.hpp(156): error C2248: 'Reactive<T>::Proxy::Proxy' : cannot access private member declared in class 'Reactive<T>::Proxy'
1> with
1> [
1> T=int
1> ]
1> main.cpp(32) : see declaration of 'Reactive<T>::Proxy::Proxy'
1> with
1> [
1> T=int
1> ]
1> main.cpp(30) : see declaration of 'Reactive<T>::Proxy'
1> with
1> [
1> T=int
1> ]
1> c:\program files (x86)\boost\boost_1_53_0\boost\signals2\detail\slot_template.hpp(81) : see reference to function template instantiation 'void boost::signals2::slot0<R,SlotFunction>::init_slot_function<F>(const F &)' being compiled
1> with
1> [
1> R=void,
1> SlotFunction=boost::function<void (void)>,
1> F=Reactive<int>::Proxy
1> ]
1> main.cpp(135) : see reference to function template instantiation 'boost::signals2::slot0<R,SlotFunction>::slot0<Reactive<T>::Proxy>(const F &)' being compiled
1> with
1> [
1> R=void,
1> SlotFunction=boost::function<void (void)>,
1> T=int,
1> F=Reactive<int>::Proxy
1> ]
1> main.cpp(178) : see reference to function template instantiation 'Reactive<T> &&Reactive<T>::operator +<int>(Reactive<T> &)' being compiled
1> with
1> [
1> T=int
1> ]
1>
1>Build FAILED.
1>
1>Time Elapsed 00:00:04.20
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
Я думаю, что у вас есть проблемы с дизайном.
Логическое время жизни реактивной переменной не должно быть привязано к времени жизни переменной C ++, которая ее называет.
Использовать pImpl
шаблон времени жизни каждой реактивной переменной, если существует переменная C ++, которая ее называет, или если существует другая активная переменная, которая ссылается на нее. (std::shared_ptr
с, может быть, чем-то, чтобы обнаружить круговые ссылки, которые в любом случае являются плохим моджо в реактивном графе).
Когда вы двигаетесь, вы перемещаете состояние внутри pImpl
, не pImpl
сам по себе pImpl
имеет свой собственный указатель на реактивное состояние (а ppImpl
), что действительно полезно для временных реактивов (таких как A+B
).
Когда вы зависите от A
вы на самом деле зависите от A->pImpl
, и вы до его счетчик ссылок.
И если вы установите A = B
, это означает, что A->pImpl
зависит от B->pImpl
не A->pImpl
Реактивное состояние является копией B->pImpl
Реактивное состояние. Это тонкая разница.
Это все еще требует определенных усилий, чтобы изменения распространялись вперед. Я бы вернулся weak_ptr
ссылки на тех, кто зависит от вас, и SetIsDirty
который распространяется назад через дерево. Все, что является грязным, пересчитывается при чтении, и в противном случае используется кэшированное значение.
Если вы хотите эффект A = B
вызывать A->pImpl
быть копией B->pImpl
используйте другой синтаксис (например, A = *B
, чтобы украсть C ++ изм). Это единственная ситуация, которая требует копирования pImpl
государство!
Обратите внимание, что A = std::move(B)
требует перемещения pImpl
состояние, следовательно, использование ppImpl
хранить состояние в целях оптимизации.
Вы должны найти, что никакие дополнительные потоки или слоты / сокеты действительно не нужны. И если вы не реализуете одинарный operator*
нет необходимости копировать состояние данного pImpl
нужно.
Обратите внимание, что мой комментарий о циклических зависимостях важен. Реактивный граф с круговой зависимостью будет течь в соответствии с вышеуказанным дизайном, и, что более важно, может быть логически невозможно вычислить любое значение.
Других решений пока нет …