Comments 50
Я правильно понимаю, что вместо
Спасибо за статью, сигналы, возвращающие объекты порадовали… интересно, кто-то использовал эту возможность не на примерах?
int
вот тут boost::signal<int(), Sum> signal
должен быть string
?Спасибо за статью, сигналы, возвращающие объекты порадовали… интересно, кто-то использовал эту возможность не на примерах?
Вот блин, а я свой велосипед писал…
Также стоит дополнить, что сигналы/слоты из boost отлично дружат с сигналами/слотами Qt. Довольно часто это бывает необходимо.
За время использования сигналов/слотов в Qt, у меня сложилось к ним неоднозначное мнение. С одной стороны, это действительно удобный и интуитивно понятный способ вызова одних объектов из других. Так и напрашиваются повесить их на кнопку, тестовое поле или что-то еще.
С другой стороны, сигналы «стреляют во Вселенную», чем очень часто пользуются разработчики. И вот тут начинается дикий геморрой, когда сигнал из одного объекта ловится совершенно никак не относящимся к нему другим объектом, от него уходит еще куда-то и т.д. И так получается, что все объекты системы взаимодействуют между собой только посредством сигналов и слотов, никакого классического ООП.
Я реально такое видел, и исправлять там что-то обычно бессмысленно — проще и лучше написать все заново.
Я не призываю не использовать сигналы/слоты, я призываю использовать их с умом.
За время использования сигналов/слотов в Qt, у меня сложилось к ним неоднозначное мнение. С одной стороны, это действительно удобный и интуитивно понятный способ вызова одних объектов из других. Так и напрашиваются повесить их на кнопку, тестовое поле или что-то еще.
С другой стороны, сигналы «стреляют во Вселенную», чем очень часто пользуются разработчики. И вот тут начинается дикий геморрой, когда сигнал из одного объекта ловится совершенно никак не относящимся к нему другим объектом, от него уходит еще куда-то и т.д. И так получается, что все объекты системы взаимодействуют между собой только посредством сигналов и слотов, никакого классического ООП.
Я реально такое видел, и исправлять там что-то обычно бессмысленно — проще и лучше написать все заново.
Я не призываю не использовать сигналы/слоты, я призываю использовать их с умом.
Как раз независимые объекты, которые обмениваются сообщениями — это самая что не на есть классика ООП. Вызов методов как в С-подобных языках это всего-лишь упрощенная реализация этого механизма. В Smalltalk, например, кажется вообще нету прямого вызова функций (да и функций как таковых) как в процедуральных языках.
Поддержу вас. Сигналы-слоты это хоть и неявное но связывание объектов, причем плохоконтролируемое. Можно связать хобот слона с его задницей, и даже не заметить сразу такого конфуза.
Поэтому да, сильно увлекаться не стоит. Механизм мощный, а потому его неосмотрительное использование разрушительно.
Поэтому да, сильно увлекаться не стоит. Механизм мощный, а потому его неосмотрительное использование разрушительно.
А как быть с потоками? Ведь слоты вызываются в треде сигнала. Можно ли переложить это в «поток обьекта»?
Я для этого использую boost::asio.
В основном потоке запускаю IoService, который вызывает run_one, а все вызовы сигнала заворачиваю в IoService.post. Получается как-то так, например:
В основном потоке запускаю IoService, который вызывает run_one, а все вызовы сигнала заворачиваю в IoService.post. Получается как-то так, например:
boost::asio::io_service IoService;
boost::signal<void(int int)> TapDownSignal;
//В чужом потоке
void Application::OnTapDown(int x, int y)
{
IoService.post(boost::bind(boost::ref(TapDownSignal), x, y));
}
//В основном потоке:
void ResourceManager::Update(int dt)
{
...
IoService.run_one();
}
Я подробностей не изучал, но есть ещё библиотека Signals2, которая «thread-safe version of Signals». Вероятно, там эти вопросы прорабатываются.
Хм, а почему не boost::signals2?
И почему boost::bind вместо std::bind? И почему версия 1.51.0 вместо актуальной?
Версию исправил.
std::bind не очень хорошо работает в Visual Studio 2010, поэтому я использую boost::bind
std::bind не очень хорошо работает в Visual Studio 2010, поэтому я использую boost::bind
а в каком плане «не очень хорошо работает в Visual Studio 2010»?
struct MyStruct
{
void method(int x)
{
}
};
boost::signal<void(int x)> mySignal;
MyStruct myStruct;
mySignal.connect(std::bind(&MyStruct::method, &myStruct, _1));
Компилятор ругается на последнюю строку многоэтажной ошибкой. Я ниасилил понять эту ошибку, поэтому избегаю std::bind.
UFO just landed and posted this here
А разве оно не только под windows?
UFO just landed and posted this here
Я посмотрел С++ версию Rx Framework (https://rx.codeplex.com/SourceControl/changeset/view/7881e17c060b#Rx/CPP/RxCpp.sln) — это оно? Я не нашел способа скомпилировать это под Android и iOS, а это для меня критично.
>Нельзя получить сигнал, который бы аггрегировал другие сигналы без кучи boilerplate кода.
Это оно?
>Нет способа управлять тем, в каком потоке и в какое время будет вызван соответствующий слот.
Это приходится делать вручную, отправляя вызов сигнала в определенный поток. Примерно так, как я писал выше. Не очень удобно, но я привык.
>Нельзя получить сигнал, который бы аггрегировал другие сигналы без кучи boilerplate кода.
boost::signal<void()> signal1;
boost::signal<void()> signal2;
signal2.connect(boost::ref(signal1));
signal2(); //Вызывает signal2, который вызывает signal1
Это оно?
>Нет способа управлять тем, в каком потоке и в какое время будет вызван соответствующий слот.
Это приходится делать вручную, отправляя вызов сигнала в определенный поток. Примерно так, как я писал выше. Не очень удобно, но я привык.
Было бы круто, ввести на хабре обязательным пояснение почему + или -. И складывать эти пояснения где-то возле ответа (но это уже детали дизайна). Тогда бы думали перед тем как тыкать.
UFO just landed and posted this here
Я вот нашел сравнение: timj.testbit.eu/2013/01/25/cpp11-signal-system-performance/
Вывод — Boost Signals это не самая быстрая реализация сигналов и слотов.
Вывод — Boost Signals это не самая быстрая реализация сигналов и слотов.
Там разница — в наносекунды. Если значения таких порядков важны — ну тогда уж надо хранить указатели на функции в массиве и вручную вызывать, а еще лучше — сразу джампами на асме писать. А в общем случае — какая разница вызовется обработчик OnKeypressed через 60 наносекунд, или через 200, если человек физически имеет реакцию на уровне 20-50 милисекунд в лучшем случае (это на 6 порядков медленнее).
UFO just landed and posted this here
Автору: А можете также лаконично и кратко описать новую Boost.Coroutine?
>… Про Boost Bind я, вероятно, напишу отдельную статью…
А вы замените его на анонимную функцию.
А вы замените его на анонимную функцию.
boost::signal<void(int, int)> SelectCell;
Забавно, что в шарпе события и обработчики «родные» для языка, а вот такого красивого и лаконичного синтаксиса там нет. События вообще не first-class citizen, а какой-то костыль. Тот же disconnect_all_slots чёрта с два нормально сделаешь, с несоответствием типов постоянные проблемы. Про проверки на null даже вспоминать не хочется. И ничего в этом направлении не происходит, даже супер-продвинутый Rx Framework с блэкджеком и шлюхами работает с событиями через отражения — ужас на курьих ножках. :(
Кстати, спортивный интерес. Вот допустим, у меня контрол, в котором 150 событий — можно ли как-то свалить все слоты в один объект и сэкономить на 150 объектах сигналов?
>Вот допустим, у меня контрол, в котором 150 событий — можно ли как-то свалить все слоты в один объект и сэкономить на 150 объектах сигналов?
Я делаю комбинацией shared_ptr и variant, не судите строго:
Я делаю комбинацией shared_ptr и variant, не судите строго:
//Variant с зараннее определенными типами данных:
typedef boost::variant<int, float, std::string, vec2> TSignalParam;
//Хранитель различных сигналов:
struct TWidgetStruct
{
protected:
//Карта сигналов, ключ - имя сигнала
std::map<std::string, std::shared_ptr<boost::signal<void (TSignalParam)>>> SignalMap;
public:
//Чистим все
void ClearSignals()
{
SignalMap.clear();
}
//Добавляем слот к сигналу
void AddSlot(std::string signalName, std::function<void (TSignalParam)>> func)
{
//Если такого сигнала еще нет - создаем
if (SignalMap[signalName] == std::shared_ptr<boost::signal<void (TSignalParam)>>())
{
SignalMap[signalName] = std::shared_ptr<boost::signal<void (TSignalParam)>>(
new boost::signal<void (TSignalParam)>());
}
//Добавляем слот
SignalMap[signalName]->connect(func);
}
};
Пример использования:
auto mouseDownFunc = [](TSignalParam param)
{
vec2 v = boost::get<vec2>(param);
std::cout<<"pressed at "<<v.x<<" "<<v.y<<std::endl;
}
auto changeTextFunc = [](TSignalParam param)
{
std::string text = boost::get<std::string>(param);
std::cout<<"text :"<<text<<std::endl;
}
TWidgetStruct WidgetStruct;
WidgetStruct.AddSlot("OnMouseDown", mouseDownFunc);
WidgetStruct.AddSlot("OnChangeText", changeTextFunc);
UFO just landed and posted this here
Тот же disconnect_all_slots чёрта с два нормально сделаешь
На мой взгляд, это именно disconnect_all_slots – костыль, потому что он позволяет снять все обработчики/слоты внешнему классу (нарушение инкапсуляции), и простого способа предотвратить это, как я понимаю, нет.
В .NET же это просто и удобно:
class MyClass
{
event EventHandler MyEvent;
void MyMethod()
{
this.MyEvent = null;
}
}
UFO just landed and posted this here
Обойтись можно, и в основном этим способом и пользуюсь, но синтаксис ужасный. Ну почему нельзя в язык добавить нормальный доступ к add/remove по имени события? Проблем с обратной совместимостью, вроде, быть не должно; и в целом цена фичи выглядит небольшой.
UFO just landed and posted this here
Имея имя события, нельзя обратиться к add и remove как методам этого события. Нельзя передать событие как аргумент: «Вот тебе событие, подпишись на него».
Вот так вы можете передать событие в функцию, а также подписаться на него:
Вот так вы, имея имя события, можете получить
class MyClass
{
event EventHandler MyEvent;
void MyMethod()
{
this.Subscribe(ref this.MyEvent, this.MyHandler);
}
// код аналогичен add_MyEvent
// можно переписать в общем виде, используя касты в System.Delegate
void Subscribe(ref EventHandler e, EventHandler handler)
{
EventHandler fetched;
EventHandler current = e;
do
{
fetched = current;
EventHandler newE = (EventHandler)Delegate.Combine(fetched, handler);
current = Interlocked.CompareExchange(ref e, newE, fetched);
}
while (current != fetched);
}
void MyHandler(object o, EventArgs e)
{
}
}
Вот так вы, имея имя события, можете получить
add_MyEvent
:Action<EventHandler> add_MyEvent =
(Action<EventHandler>)
typeof(MyClass)
.GetEvent("MyEvent", BindingFlags.NonPublic | BindingFlags.Instance)
.GetAddMethod(true)
.CreateDelegate(typeof(Action<EventHandler>), myClass);
// myClass – экземпляр MyClass
Вот так вы можете передать событие в функцию, а также подписаться на него:
Возможно только внутри класса, который определяет событие.
Вот так вы, имея имя события, можете получить add_MyEvent
Дык отражения же, по сути хак — ни строгой типизации, ни нормального рефакторинга. О том и речь.
Возможно только внутри класса, который определяет событие.Ссылку за пределы класса можно вывести через callback-и. Несколько неудобно, да. С другой стороны, мне ещё никогда не приходилось передавать событие как аргумент. Предпочитаю IoC событийно-ориентированному подходу.
С другой стороны, мне ещё никогда не приходилось передавать событие как аргумент.
Reactive Extensions не доводилось пользоваться? В основном на стыке между Rx и традиционным кодом с событиями такая проблема и возникает. IoC и коллбэки проблему не решают, потому что в .NET события везде и всюду, свои решения в сам фреймворк не запихнуть.
> Кстати, спортивный интерес. Вот допустим, у меня контрол, в котором 150 событий
Если вы про .net, то посмотрите на WinForns, там как раз так и организовано, чтобы не возить с собой 100500 объектов событий, на большинство которых так никто и не подпишется (т.н. «sparse events»)
Если вы про .net, то посмотрите на WinForns, там как раз так и организовано, чтобы не возить с собой 100500 объектов событий, на большинство которых так никто и не подпишется (т.н. «sparse events»)
Я правильно понимаю что сингалы — это те-же многоадресные делегаты?
Sign up to leave a comment.
Boost Signals — сигналы и слоты для C++