Question

Problem

I've got a C-like API, that I don't have control over, with functions to register/unregister event callbacks:

enum Event { Evt1, Evt2, Evt3 }; // events generated by API library
typedef void(__cdecl *Callback)(Event e, void* context);
void API_add_callback(Event e, Callback cb, void* context);
void API_remove_callback(Event e, Callback cb, void* context);

I went to create a wrapper base class APIClient to encapsulate this API like so:

class APIClient
{
  public:
    APIClient(){}

  protected:

    // this is to be used by subclasses
    void subscribe(const set<Event>& events)
    {
      _events = events;
      set<Event>::const_iterator it;
      for (it = _events.begin(); it != _events.end(); ++it)
      {
        API_add_callback(e, &callback, this);
      }
    }

    // this is to be used by subclasses
    void unsubscribe()
    {
      set<Event>::const_iterator it;
      for (it = _events.begin(); it != _events.end(); ++it)
      {
        API_remove_callback(e, &callback, this);
      }
    }

    // this is to be implemented by subclasses
    virtual void on_event(Event e) = 0;

  private:

    // this is the proxy callback that we register for all events
    static void __cdecl callback(Event e, void* context)
    {
      APIClient* instance = (APIClient*)context;

      // forward the event to the subclass
      instance->on_event(e);
    }

    set<Event> _events;
};

So far so good, I thought. Then I made two subclasses, Foo and Bar that are APIClients:

// This one is interested in Evt1 and Evt2 of the API...
class Foo : public APIClient
{
  public:

    Foo() : APIClient()
    {
      set<Event>s;
      s.insert(Evt1);
      s.insert(Evt2);
      subscribe(s);
    }

    ~Foo()
    {
      unsubscribe();
    }

  protected:

    virtual void on_event(Event e)
    {
      // here e will be Evt1 or Evt2, whenever they are fired
      // by the API
    }
};

// And this one is interested in Evt2 and Evt3 of the API...
class Bar : public APIClient
{
  public:

    Bar() : APIClient()
    {
      set<Event>s;
      s.insert(Evt2);
      s.insert(Evt3);
      subscribe(s);
    }

    ~Bar()
    {
      unsubscribe();
    }

  protected:

    virtual void on_event(Event e)
    {
      // here e will be Evt2 or Evt3, whenever they are fired
      // by the API
    }
};

Trouble is, it doesn't work ... because the library behind the API determines a unique subscription based on event and callback, not the context (the context is just additional, optional, user data). So, in general, as it turned out, after

API_add_callback(Evt2, &callback, instance_of_Foo);
API_add_callback(Evt2, &callback, instance_of_Bar);

only the second subscription wins, so Foo never hears of Evt2.

My failed solution attempt

Since is appears that the API requires a specific callback (i.e. a different address) for every new subscription for the same event I thought: code generation ... templates! After templatizing APIClient, something like &APIClient<Foo>::callback and &APIClient<Bar>::callback should give me different addresses, right? Wrong! It only generates different addresses (that is, different functions) if they're sufficiently different.

So

template<typename T>
class APIClient
{
    // ... other code ...

    static void __cdecl callback(Event e, void* context)
    {
      APIClient* instance = (APIClient*)context;

      // forward the event to the subclass
      instance->on_event(e);
    }
}

is no good. But the following would force a template instantiation for T=Foo and T=Bar, thus giving me &APIClient<Foo>::callback != &APIClient<Bar>::callback:

template<typename T>
class APIClient
{
    // ... other code ...

    static void __cdecl callback(Event e, void* context)
    {
      APIClient* instance = (APIClient*)context;

      // Use T here explicitely to force different template
      // instantiations of APIClient<T>::callback
      T::call_something();

      // forward the event to the subclass
      instance->on_event(e);
    }
}

This is no good. It looks like I'm trying to outsmart the compiler to make it generate what seems to be redundant code, and I think I am doomed to fail :)

Question (finally)

  • Is there a clean way (without singletons and all) to solve my original problem?
  • Is there a way to do it the template way, making sure that I get different functions for each instantiation of APIClient<T>::callback without doing anything ugly?

Note: C++11 is ruled out, unfortunately.

Was it helpful?

Solution

This something called the curiously recurring template pattern (http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern)

template<typename T>
class APIClient
{
  public:
    APIClient(){}

  protected:

    // this is to be used by subclasses
    void subscribe(const set<Event>& events)
    {
      _events = events;
      set<Event>::const_iterator it;
      for (it = _events.begin(); it != _events.end(); ++it)
      {
         API_add_callback(it, &(T::callback), this);
      }
    }

    // this is to be used by subclasses
    void unsubscribe()
    {
      set<Event>::const_iterator it;
      for (it = _events.begin(); it != _events.end(); ++it)
      {
        API_remove_callback(it, &(T::callback), this);
      }
    }

  private:

    // this is the proxy callback that we register for all events
    static void __cdecl callback(Event e, void* context)
    {
       T * instance = (T*)context;

      // forward the event to the subclass
      instance->on_event(e);
    }

    set<Event> _events;
};

The important change is that callback now uses T * instance. This makes access an issue. You have a few options on how to get around that. You can leave on_event as virtual or remove it from APIClient completely as I have done. The sub-classes still need to implement it, but it can either be public or APIClient can be a friend of the sub-classes.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top