Boost :: signals2 transmitting invalid data

I've been a C / C ++ developer for 20 years now, but templates have always been a weak point for me. As template programming becomes more useful and complex, in the C ++ 11 and C ++ 14 standards, I decided to try an exercise to learn. I have been moderately successful, but I have a problem that I am having problems with. I have the following class:

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;
};

      

This code potentially ends up in a large structure that loads several modules, which are dynamic link libraries on linux. The idea would be for a given module to call:

consume_event<ParamTypes><EventNumber, SomeCallack)

      

The callback can be a function with void signature (ParamTypes) or the result of std :: bind () for a function with void signature (ParamTypes).

Then another module will be able to call:

emit<ParamTypes>(EventNumber, ParamValues) 

      

and every module that called consume_event would have a handler with ParamValues.

This seems to work in almost every case, except when I pass a reference to a custom class like:

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

      

In this case, the function connected to the signal receives 0xa and crashes immediately when it tries to process it as ip_stats_t &.

Output:

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.

      

Update: I just noticed that it does the same thing, passing any variable by reference, not just the custom class above.

Also, note that there is no SSCCE in this question , because any immutable SSCCE works. The problem does not arise until the working code is placed in the above structure.

Update2: The real question is how can this design be improved. Not only does this one not work properly, but syntactically, it stinks. it’s ugly, inelegant, and really, there’s nothing good about it, other than doing what I wanted him to do and increasing my understanding of patterns.

Update3: I have now 100% confirmed that this has nothing to do with the datatype I am passing. If I pass any variable by reference, the slot always gets 0xa as the reference address. This includes std :: strings and even ints. If I pass any variable by value, the copy constructor of that value eventually gets 0xa as a reference for the value to be copied. This only happens when calling a slot in module B from a signal generated in module A. What am I missing?

Any ideas? Thank!

+3


source to share


2 answers


UPDATED I have since come up with a demo that would be closer to what you were trying to achieve:

@ lk75 For fun, here's an approach that abstracts the event mechanism in a fairly extensible way, and

  • not too difficult
  • does not require the calling signature to be repeated throughout the place (it is now in Traits

    )
  • no signal leakage using true RAII style (SCNR). More use new

    or delete

    !

Look . Live On Coliru

Notice how I have simplified and made both singleton consume_event

and emit

in the one-line:

    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)...);
    }

      

Complete code

For reference:

Live On Coliru

#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 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
}

      


Old answer:

You seem to have a problem with variable forwarding:



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

      

Also, forwarding only really makes sense when accepting arguments using a "universal reference":

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

      

However, you don't rely on argument type inference to get the actual value categories (rvalue vs. lvalue). And rightly so (because the compiler will most likely never get the exact types of the "right" arguments to match the stored signal (otherwise fail any_cast

or call Undefined Behavior at best.)

So, in this case, you should give up all the shipping business:

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();
    }
}

      

Full demo program: Live On Coliru

#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


source


The following code, which is Sehe's revised code without boost :: signals, solved my problem completely. It looks like boost :: signal had problems passing any data within module bounds. Replacing it with a simple vector of functions works in all cases, and faster anyway!



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


source







All Articles