Pull to refresh

Comments 50

Я правильно понимаю, что вместо int вот тут boost::signal<int(), Sum> signal должен быть string?
Спасибо за статью, сигналы, возвращающие объекты порадовали… интересно, кто-то использовал эту возможность не на примерах?
Спасибо, исправил.
Я использую сигналы и слоты в UI в своем движке. Очень удобно. Уже словил, кстати, несколько грабель — например, нельзя во время вызова сигнала что-то подключать к нему и отключать.
думаю, все через это проходили, куда же без этого, а потом оказывалось, что всё уже давно есть в std::tr1::function, например.
Также стоит дополнить, что сигналы/слоты из boost отлично дружат с сигналами/слотами Qt. Довольно часто это бывает необходимо.

За время использования сигналов/слотов в Qt, у меня сложилось к ним неоднозначное мнение. С одной стороны, это действительно удобный и интуитивно понятный способ вызова одних объектов из других. Так и напрашиваются повесить их на кнопку, тестовое поле или что-то еще.
С другой стороны, сигналы «стреляют во Вселенную», чем очень часто пользуются разработчики. И вот тут начинается дикий геморрой, когда сигнал из одного объекта ловится совершенно никак не относящимся к нему другим объектом, от него уходит еще куда-то и т.д. И так получается, что все объекты системы взаимодействуют между собой только посредством сигналов и слотов, никакого классического ООП.
Я реально такое видел, и исправлять там что-то обычно бессмысленно — проще и лучше написать все заново.
Я не призываю не использовать сигналы/слоты, я призываю использовать их с умом.
Как раз независимые объекты, которые обмениваются сообщениями — это самая что не на есть классика ООП. Вызов методов как в С-подобных языках это всего-лишь упрощенная реализация этого механизма. В Smalltalk, например, кажется вообще нету прямого вызова функций (да и функций как таковых) как в процедуральных языках.
Поддержу вас. Сигналы-слоты это хоть и неявное но связывание объектов, причем плохоконтролируемое. Можно связать хобот слона с его задницей, и даже не заметить сразу такого конфуза.
Поэтому да, сильно увлекаться не стоит. Механизм мощный, а потому его неосмотрительное использование разрушительно.
А как быть с потоками? Ведь слоты вызываются в треде сигнала. Можно ли переложить это в «поток обьекта»?
Я для этого использую boost::asio.
В основном потоке запускаю 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». Вероятно, там эти вопросы прорабатываются.
Signals 2 — потокобезопасная реализация с тем-же интерфейсом что и у Signals, вопросами диспетчеризации сообщений между потоками она, к сожалению, не занимается.
И почему boost::bind вместо std::bind? И почему версия 1.51.0 вместо актуальной?
Версию исправил.
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.
Потому что _1 находится в пространстве std::placeholders, вроде с s на конце. А в бусте в boost.
Спасибо, исправил _1 на std::placeholders::_1 — теперь заработало. Буду знать.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Я посмотрел С++ версию Rx Framework (https://rx.codeplex.com/SourceControl/changeset/view/7881e17c060b#Rx/CPP/RxCpp.sln) — это оно? Я не нашел способа скомпилировать это под Android и iOS, а это для меня критично.

>Нельзя получить сигнал, который бы аггрегировал другие сигналы без кучи boilerplate кода.
boost::signal<void()> signal1;
boost::signal<void()> signal2;

signal2.connect(boost::ref(signal1));

signal2(); //Вызывает signal2, который вызывает signal1

Это оно?

>Нет способа управлять тем, в каком потоке и в какое время будет вызван соответствующий слот.
Это приходится делать вручную, отправляя вызов сигнала в определенный поток. Примерно так, как я писал выше. Не очень удобно, но я привык.

UFO just landed and posted this here
Было бы круто, ввести на хабре обязательным пояснение почему + или -. И складывать эти пояснения где-то возле ответа (но это уже детали дизайна). Тогда бы думали перед тем как тыкать.
UFO just landed and posted this here
Там разница — в наносекунды. Если значения таких порядков важны — ну тогда уж надо хранить указатели на функции в массиве и вручную вызывать, а еще лучше — сразу джампами на асме писать. А в общем случае — какая разница вызовется обработчик OnKeypressed через 60 наносекунд, или через 200, если человек физически имеет реакцию на уровне 20-50 милисекунд в лучшем случае (это на 6 порядков медленнее).
UFO just landed and posted this here
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, не судите строго:

//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 события везде и всюду, свои решения в сам фреймворк не запихнуть.
Нет, не доводилось. Когда-то хотел познакомиться, но, посмотрев в код реального проекта и увидев монструозные малопонятные конструкции, я быстро ретировался. С тех пор и использую везде IoC – и в ASP.NET, и в WPF – и прекрасно себя чувствую.
> Кстати, спортивный интерес. Вот допустим, у меня контрол, в котором 150 событий

Если вы про .net, то посмотрите на WinForns, там как раз так и организовано, чтобы не возить с собой 100500 объектов событий, на большинство которых так никто и не подпишется (т.н. «sparse events»)
Я правильно понимаю что сингалы — это те-же многоадресные делегаты?
Sign up to leave a comment.

Articles