Boost :: сигналы2, передавая неверные данные

Я работаю на C / C ++ около 20 лет, но шаблоны всегда были для меня слабым местом. В связи с тем, что программирование шаблонов становится все более полезным и сложным в стандартах C ++ 11 и C ++ 14, я решил попробовать учиться. Я был довольно успешным, но у меня есть проблема, с которой у меня проблемы. У меня есть следующий класс:

namespace Events {
// Place your new EventManager events here
static const uint32_t StatsData = 0;
static const uint32_t StatsRequest = 1;
static const uint32_t StatsReply = 2;
static const uint32_t ApplianceStatsRequest = 3;
static const uint32_t ApplianceStatsReply = 4;
static const uint32_t NullEvent = 5;
};

class EventManager {
public:
static EventManager *instance() {
if (Instance)
return Instance;

return new EventManager();
};

static void destroy() {
delete Instance;
Instance = nullptr;
}

template<typename T>
bool consume_event(uint32_t event, std::function<T> func) {
if (_event_map.find(event) == _event_map.end())
// Create the signal, in true RAII style
_event_map[event] = new boost::signals2::signal<T>();

boost::any_cast<boost::signals2::signal<T> *>(_event_map[event])->connect(func);

return true;
}

void emit(uint32_t event) {
if (_event_map.find(event) == _event_map.end())
return;

try {
boost::signals2::signal<void()> *sig =
boost::any_cast<boost::signals2::signal<void()> *>(_event_map[event]);

(*sig)();
}
catch (boost::bad_any_cast &e) {
SYSLOG(ERROR) << "Caught instance of boost::bad_any_cast: " << e.what();
abort();
}
}

template<typename... Args>
void emit(uint32_t event, Args... args) {
if (_event_map.find(event) == _event_map.end())
return;

try {
boost::signals2::signal<void(Args...)> *sig =
boost::any_cast<boost::signals2::signal<void(Args...)> *>(_event_map[event]);
(*sig)(args...);
}
catch (boost::bad_any_cast &e) {
SYSLOG(ERROR) << "Caught instance of boost::bad_any_cast: " << e.what();
abort();
}
}

private:
EventManager() { Instance = this; };
~EventManager() { Instance = nullptr; };

static EventManager *Instance;
std::map<uint32_t, boost::any> _event_map;
};

Этот код потенциально может войти в большую среду, которая загружает несколько модулей, которые являются динамическими библиотеками в Linux. Идея заключается в том, чтобы данный модуль мог вызывать:

consume_event<ParamTypes><EventNumber, SomeCallack)

Обратный вызов может быть функцией с подписью void (ParamTypes) или результатом std :: bind () для функции с подписью void (ParamTypes).

Затем другой модуль сможет вызывать:

emit<ParamTypes>(EventNumber, ParamValues)

и каждый модуль, который вызвал потребление_эвент, должен иметь свой обработчик, вызываемый с помощью ParamValues.

Кажется, это работает почти во всех случаях, кроме случаев, когда я передаю ссылку на пользовательский класс, например так:

std::cout << "Sending stats data with ref: " << std::hex << ip_entry.second <<  std::endl;
emit<ip_stats_t &>(Events::StatsData, *ip_entry.second);

В этом случае функция, которая подключена к сигналу, получает 0xa и быстро падает, когда пытается обработать его как ip_stats_t. &,

Выход:

Sending stats data with ref: 0x7fbbc4177d50 <- This is the output of the line seen above
ips addr: 0xa << this is from the function that gets called by the signal.

Обновление: я только что заметил, что это делает то же самое при передаче любой переменной по ссылке, а не только над пользовательским классом выше.

Кроме того, обратите внимание, что нет SSCCE в этом вопросе потому что любой SSCCE неизменный работает. Проблема не возникает, пока рабочий код не помещен в вышеупомянутую структуру.

Обновление 2: реальный вопрос здесь в том, как сделать этот дизайн лучше. Этот не только не работает должным образом, но синтаксически, он воняет. это уродливо, не элегантно, и действительно, в этом нет ничего хорошего, кроме того, что он сделал то, что я хотел, и улучшил мое понимание шаблонов.

Обновление 3: теперь я на 100% подтвердил, что это не имеет ничего общего с типом данных, которые я передаю. Если я пройду любой переменная по ссылке, слот всегда получает 0xa в качестве адреса ссылки. Это включает в себя std :: strings и даже int. Если я передаю любую переменную по значению, конструктор копирования этого значения в конечном итоге получает 0xa в качестве ссылки на значение, из которого копируется. Это происходит только при вызове слота в модуле B из сигнала, созданного в модуле A. Чего мне не хватает?

Есть идеи?
Спасибо!

3

Решение

ОБНОВЛЕНО С тех пор я придумал демонстрацию, которая, казалось бы, была ближе к тому, чего вы пытались достичь:

@ lk75 Для забавы, вот подход, который абстрагирует механизм событий довольно расширяемым способом, в то время как

  • не слишком сложный
  • не требующий повторной подписи вызова (это в Traits сейчас)
  • не пропуская сигналы, используя истинный стиль RAII (SCNR). Нет больше использования new или же delete!

Видеть это Жить на Колиру.

Обратите внимание, как я упростил синглтон и повернул оба consume_event а также emit в одну строку сейчас:

    static EventManager& instance() {
static EventManager instance;
return instance;
};

template <EventId event, typename F>
bool consume_event(F&& func) {
get_slot<event>().connect(std::forward<F>(func));
return true;
}

template <EventId event, typename... Args>
void emit(Args&&... args) {
get_slot<event>()(std::forward<Args>(args)...);
}

Полный код

Для справки:

#include <boost/any.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/signals2/signal.hpp>
#include <iostream>
#include <memory>
#include <string>

struct ip_stats_t {
std::string canary;
};

enum class EventId : uint32_t {
// Place your new EventManager events here
StatsData             = 0,
StatsRequest          = 1,
StatsReply            = 2,
ApplianceStatsRequest = 3,
ApplianceStatsReply   = 4,
NullEvent             = 5, // Not implemented
};

namespace Events {

template <EventId> struct Traits;

template <> struct Traits<EventId::StatsData>             { using signal_type = boost::signals2::signal<void(int)>;                 } ;
template <> struct Traits<EventId::StatsRequest>          { using signal_type = boost::signals2::signal<void(bool, bool)>;          } ;
template <> struct Traits<EventId::StatsReply>            { using signal_type = boost::signals2::signal<void(std::string)>;         } ;
template <> struct Traits<EventId::ApplianceStatsRequest> { using signal_type = boost::signals2::signal<void(double, ip_stats_t&)>; } ;
//template <> struct Traits<EventId::NullEvent>             { using signal_type = boost::signals2::signal<void()>;                    } ;

template <> struct Traits<EventId::ApplianceStatsReply> : Traits<EventId::ApplianceStatsRequest> { };
}

class EventManager {
public:
static EventManager& instance() {
static EventManager instance;
return instance;
};

template <EventId event, typename F>
bool consume_event(F&& func) {
get_slot<event>().connect(std::forward<F>(func));
return true;
}

template <EventId event, typename... Args>
void emit(Args&&... args) {
get_slot<event>()(std::forward<Args>(args)...);
}

private:
template <EventId event, typename Slot = typename Events::Traits<event>::signal_type, typename SlotPtr = boost::shared_ptr<Slot> >
Slot& get_slot() {
try {
if (_event_map.find(event) == _event_map.end())
_event_map.emplace(event, boost::make_shared<Slot>());

return *boost::any_cast<SlotPtr>(_event_map[event]);
}
catch (boost::bad_any_cast const &e) {
std::cerr << "Caught instance of boost::bad_any_cast: " << e.what() << " on event #" << static_cast<uint32_t>(event) << "\n";
abort();
}
}

EventManager() = default;
std::map<EventId, boost::any> _event_map;
};

int main() {
auto& emgr = EventManager::instance();

emgr.consume_event<EventId::ApplianceStatsRequest>([](double d, ip_stats_t& v) {
std::cout << "d: " << d << ", v.canary: " << v.canary << "\n";
});
emgr.consume_event<EventId::ApplianceStatsRequest>([](double d, ip_stats_t& v) {
std::cout << "And you can register more than one\n";
});ip_stats_t v { "This is statically checked" };
emgr.emit<EventId::ApplianceStatsRequest>(3.142f, v);

emgr.emit<EventId::StatsData>(42); // no connection, but works
emgr.consume_event<EventId::StatsData>([](int) { std::cout << "Now it's connected\n"; });
emgr.emit<EventId::StatsData>(42); // now with connection!

#if 0
emgr.emit<EventId::ApplianceStatsRequest>();  // error: no match for call to ‘(boost::signals2::signal<void(double, ip_stats_t&)>) ()’
emgr.consume_event<EventId::NullEvent>([]{}); // use of incomplete type Traits<NullEvent>
#endif
}

Старый ответ:

Похоже, у вас проблемы с пересылкой с переменным значением:

    (*sig)(std::forward<Args>(args)...);

Кроме того, пересылка действительно имеет смысл, только если аргументы взяты из «универсальной ссылки»:

template<typename... Args>
void emit(uint32_t event, Args&&... args) { // NOTE!!

Однако вы не полагаетесь на вывод типа аргумента для получения категорий фактических значений (rvalue против lvalue). А также, справедливо (потому что компилятор, вероятно, никогда не получит точные типы аргументов «правильно», чтобы соответствовать хранимому сигналу (делая any_cast в лучшем случае, или вызвать Неопределенное поведение в лучшем случае.)

Так что в этом случае вам следует обойтись без всего экспедиторского бизнеса:

template<typename... Args> using Sig = boost::signals2::signal<void(Args...)>;

template<typename... Args>
void emit(uint32_t event, Args... args) {
if (_event_map.find(event) == _event_map.end())
return;

try {
Sig<Args...> *sig = boost::any_cast<Sig<Args...> *>(_event_map[event]);

(*sig)(args...);
}
catch (boost::bad_any_cast &e) {
std::cerr << "Caught instance of boost::bad_any_cast: " << e.what();
abort();
}
}

Полная демонстрационная программа: Жить на Колиру

#include <boost/any.hpp>
#include <boost/signals2/signal.hpp>
#include <iostream>
#include <string>

struct ip_stats_t {
std::string canary;
};

template<typename... Args> using Sig = boost::signals2::signal<void(Args...)>;
std::map<uint32_t, boost::any> _event_map;

template<typename... Args>
void emit(uint32_t event, Args&&... args) {
if (_event_map.find(event) == _event_map.end())
return;

try {
Sig<Args...> *sig = boost::any_cast<Sig<Args...> *>(_event_map[event]);

(*sig)(std::forward<Args>(args)...);
}
catch (boost::bad_any_cast &e) {
std::cerr << "Caught instance of boost::bad_any_cast: " << e.what();
abort();
}
}

int main()
{
Sig<int, double> sig1;
Sig<ip_stats_t&> sig2;

sig1.connect([](int i, double d) { std::cout << "Sig1 handler: i = " << i << ", d = " << d << "\n"; });
sig2.connect([](ip_stats_t& v)   { std::cout << "Sig2 handler: canary = " << v.canary << "\n"; });

_event_map[1] = &sig1;
_event_map[2] = &sig2;

emit<int, double>(1, 42, 3.14);

ip_stats_t instance { "Hello world" }, *ptr = &instance;

emit<ip_stats_t&>(2, *ptr);
}
3

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

Следующий код, который является пересмотренным кодом Sehe без boost :: сигналов, полностью решил мою проблему. Похоже, что у boost :: signal возникли проблемы с передачей любых данных через границы модуля. Замена его простым вектором функций работает во всех случаях и в любом случае быстрее!

enum class EventId : uint32_t {
// Place your new EventManager events here
StatsData             = 0,
StatsRequest          = 1,
StatsReply            = 2,
ApplianceStatsRequest = 3,
ApplianceStatsReply   = 4,
};

struct ip_stats_t;

namespace Events {
template <EventId> struct Traits;

template <> struct Traits<EventId::StatsData>
{ using signal_vec = std::vector<std::function<void(ip_stats_t &)>>; } ;

template <> struct Traits<EventId::StatsRequest>
{ using signal_vec = std::vector<std::function<void(std::ostream &)>>; } ;

template <> struct Traits<EventId::StatsReply>
{ using signal_vec = std::vector<std::function<void(std::string &)>>; } ;

template <> struct Traits<EventId::ApplianceStatsRequest> :
Traits<EventId::StatsRequest> {};

template <> struct Traits<EventId::ApplianceStatsReply> :
Traits<EventId::StatsReply> {};
}

class EventManager {
public:
static EventManager& instance() {
static EventManager instance;
return instance;
};

template <EventId event, typename F>
void consume_event(F&& func) {
get_slot<event>().push_back(std::forward<F>(func));
}

template <EventId event, typename... Args>
void emit(Args&&... args) {
for (auto &vi : get_slot<event>()) {
vi(std::forward<Args>(args)...);
}
}

private:
template <EventId event,
typename Slot = typename Events::Traits<event>::signal_vec,
typename SlotPtr = std::shared_ptr<Slot>>
Slot& get_slot() {
if (_event_map.find(event) == _event_map.end())
_event_map.emplace(event, std::make_shared<Slot>());

try {
return *boost::any_cast<SlotPtr>(_event_map[event]);
}
catch (boost::bad_any_cast const &e) {
std::cerr << e.what() << " on event #" << static_cast<uint32_t>(event) << "\n";
abort();
}
}

EventManager() = default;
std::map<EventId, boost::any> _event_map;
};
0

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