Почему для boost :: signal2 :: signal & lt; T & gt; :: connect требуются конструкторы копирования?

Я работаю над объектом, который представляет нормальные значения данных, использующие функционально-реактивное программирование, которые изменяют свои значения при изменении зависимого значения. Я имею в виду, скажем, у вас есть 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 ==========

1

Решение

Я думаю, что у вас есть проблемы с дизайном.

Логическое время жизни реактивной переменной не должно быть привязано к времени жизни переменной 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 нужно.

Обратите внимание, что мой комментарий о циклических зависимостях важен. Реактивный граф с круговой зависимостью будет течь в соответствии с вышеуказанным дизайном, и, что более важно, может быть логически невозможно вычислить любое значение.

2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]