3

I've been a C/C++ developer for about 20 years now, but templates have always been a weak spot for me. With template programming becoming ever more useful, and complicated, in the C++11 and C++14 standards, I decided to try an exercise to learn. I've been moderately successful, but I have an issue I'm 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 would potentially go into a large framework that loads multiple modules which are dynamic libraries on linux. The idea would be for a given module to be able to call:

consume_event<ParamTypes><EventNumber, SomeCallack)

The callback may be a function with signature void(ParamTypes), or the result of std::bind() on a function with signature void(ParamTypes).

Another module would then be able to call:

emit<ParamTypes>(EventNumber, ParamValues) 

and each module that had called consume_event, would have it's handler called with ParamValues.

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

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 that is connected to the signal, receives 0xa, and promptly crashes when it tries to treat it as an ip_stats_t &.

The output is:

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 it does the same thing when passing any variable by reference, not just the custom class above.

Additionally, please note that there is no SSCCE in this question because any SSCCE invariable works. The problem does not occur until the working code is put into the above framework.

Update2: The real question here is, how can this design be made better. This one not only doesn't work properly, but syntactically, it stinks. it's ugly, inelegant, and really, there's nothing good about it, except that it did what I wanted it to do and increased my understanding of templates.

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

Any ideas? Thanks!

lk75
  • 61
  • 1
  • 7
  • not shown: check that `ip_entry.second` is not a stale pointer (e.g. to a stack object that went out of scope, or to a heap object that was deleted) – sehe Oct 13 '14 at 19:23
  • Thank you, it is not a stale pointer for sure. These are pointers stored in a map that don't get deleted until the entire class containing it get deleted. Just to make sure, I checked the data, via GDB, and it is valid. – lk75 Oct 13 '14 at 19:26
  • Good to have that out of the way! – sehe Oct 13 '14 at 19:45
  • In your added code you still don't show the relevant moving parts. How do you call `consume_event<>` and `emit<>` for the failing case? Please, make a SSCCE for this. You'll see the problem in no time. (Also, are you calling across dynamic module boundaries?) – sehe Oct 13 '14 at 21:12
  • Side note: "In true RAII style" - it could hardly be farther away from that, really. RAII is where lifetime controls resource acquisition. (Yeah. The name is confusing. Even Stroustrup agrees). – sehe Oct 13 '14 at 21:18
  • I know, the RAII thing was a joke for a colleague :) As I posted above, an SSCCE for this isn't possible, because I can take this exact code and move it out into a standalone process, and it works fine. Originally, I thought I had done something stupid with the templating which would be obvious to someone else. – lk75 Oct 13 '14 at 22:23
  • 1
    Thank you for looking, but I think question http://stackoverflow.com/questions/7957239/boost-signals2-automatic-connection-management-and-changing-the-mutex-type-of-a?rq=1 has some interesting possibilities for what I'm trying to do. It seems to implement what you suggested before with a single void() signal and using std::bind() to pass the params. I'm going to give that a shot. Thanks for your input! – lk75 Oct 13 '14 at 22:23

2 Answers2

3

UPDATED I've since come up with a demonstration that would appear to 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, while

  • not being overly complicated
  • not requiring calling signature to be repeated all over the place (it's in Traits now)
  • not leaking signals by using true RAII style (SCNR). No more use of new or delete!

See it Live On Coliru.

Note how I simplified the singleton and turned both consume_event and emit into one-liners now:

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

Full 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'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
}

Old answer:

You seem to have trouble with the variadic forwarding:

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

Also, forwarding really makes sense only when taking the arguments by "universal reference":

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

However, you do not rely on argument type deduction to get the actual value categories (rvalue vs. lvalue). And, rightly so (because the compiler would likely never get the exact argument types "right" to match the stored signal (making the any_cast fail at best, or invoke Undefined Behaviour at best.)

So in this case, you should dispense with the whole forwarding 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);
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thank you for this great answer. The std::forward is actually a holdover that I forgot to delete when I tried the code with universal references. Unfortunately, removing the std::forward had no effect. I still have the same problem – lk75 Oct 13 '14 at 19:31
  • @lk75 Did you run my code? See what's different. Perhaps this will help you make the **[SSCCE](http://www.sscce.org/)** that you didn't manage to include in your question (see [Solve your problem by almost asking a question on StackOverflow](http://blog.jerryorr.com/2014/04/solve-your-problem-by-almost-asking.html)) – sehe Oct 13 '14 at 19:36
  • Oh, and I forgot to mention: it looks like you're really over complicating things badly. You should probably just use a fix signature `signal` to which you then connect _bound handlers_ (using `boost::bind` or c++11 lambda expressions). Then you can simply drop the `any`+`any_cast` – sehe Oct 13 '14 at 19:38
  • Either that, or don't try to make `emit` more generic than it is:You are having to duplicate the expected argument types on all call sites now, that's a Bad Idea(TM) and a code smell. Why not just `void emit1(int,double)` and `void emit2(ip_stats_t&)` and avoid all of the fake dynamism and maintenance nightmare of invoking `emit<>` by explicit template arguments? – sehe Oct 13 '14 at 19:41
  • Well, as I said, this is a learning exercise for template programming, not production code. That being said, I was trying to come up with a dynamic event system for a rather large project that I wrote and maintain. It's a framework that loads multiple .so modules. The EventManager class that contains the above code is housed in a header file that is included by the framework code as well as all the module code. – lk75 Oct 13 '14 at 19:56
  • The idea was for a module that wants to receive and event to simply call consume_event(EventName, [SomeFunc|std::bind(SomeFunc...)]). Then, the sender would simply need to emit(EventName, ParamVals). – lk75 Oct 13 '14 at 19:56
  • I'm not sure I completely understand what you mean by using a fixed signature and boost::bind or std::bind I guess. How would that work? The paramaters would still have to go through the signal, even with a bound function, wouldn't they? – lk75 Oct 13 '14 at 19:58
  • @lk75 The point is, I cannot possibly guess your intention, because it's not shown. So, you _may_ want to bind them, or you _may_ want to refrain form force-cramming all the signals into one map. It makes no sense (for the code shown) and leads to very convoluted call sites. To me that should prompt a reconsidering of the design choices. – sehe Oct 13 '14 at 20:12
  • Keeping in mind that I am brand new to template programming, how would I go about refraining from force-cramming them all into one map? Trust me, I racked my brain for hours on that problem. I would love nothing more than to get rid of boost::any + cast in this code. I thought I explained my intention clearly in the comments above. How can I help you help me? I can post the entire class, but I'm not sure that would give you extra information. – lk75 Oct 13 '14 at 20:37
  • I have updated the original post with the entire class in question. I also added more text to convey my intention. I apologize if I didn't give you enough information to work with, I was trying to be concise. I realize the above is poor design, but again, I suck at templates right now. How can I improve this design? – lk75 Oct 13 '14 at 20:47
  • @lk75 For fun, here's an approach that abstracts the event mechanism in a fairly extensible way, while **(a)** not being overly complicated **(b)** not requiring calling signature to be repeated all over the place (it's in `Traits` now) **(c)** not leaking signals by using true RAII style (SCNR). See it **[Live On Coliru](http://coliru.stacked-crooked.com/a/53d4633d585d8325)**. Note how I simplified the singleton and turned both `consume_event` and `emit` into one-liners now. – sehe Oct 13 '14 at 23:23
  • This looks really interesting. This is basically the implementation of the idea I had, but didn't mention, of storing the signal type with the event number, but had no idea how to do it without using a template struct or class, which would put me right back in the same position I was in before. I'll give this a shot and let you know how it works. Thanks! – lk75 Oct 14 '14 at 03:37
  • 1
    Nice clean implementation. I like it a lot, and it taught me a little more about programming with templates. Unfortunately, it still crashes in the exact same spot, for the exact same reason. At this point, I've tried 5 different implementations, 2 of yours, and 3 of my own, and nothing works. It's got to have something to do with passing data between modules, though I don't understand what the issue would be with passing data by value, which also crashes in the same way. Thanks again for your time! – lk75 Oct 14 '14 at 04:40
  • 1
    @lk75 at this point, I'd say you have a ODR violation/ABI incompatibility (meaning your dynamic library might have a different idea of what a `ip_stat_t` looks like, e.g.). Compiler flags, compiler versions, preprocessor directives etc. can subtly change the meaning and layout of a type (think alignment, padding, integer sizes etc). Hope you find it. Maybe you can make a minimal breaking sample if you can't isolate the culprit. – sehe Oct 14 '14 at 06:42
0

The following code, which is Sehe's revised code without the boost::signals solved my problem completely. It would appear that boost::signals was having issues passing any data whatsoever across module boundries. Replacing it with a simple vector of functions works in all cases, and is 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;
};
lk75
  • 61
  • 1
  • 7