4
\$\begingroup\$

Below is the code for my templated callback implementation. Currently it works for a single parameter. One thing I was going to try and do next was increase the argument to the function from 1..N arguments. Ideally this would be as simple as adding Args... everywhere using variadic templates. I don't have a lot of experience yet with variadic templates so any advice on this is helpful.

#include <iostream>
#include <vector>

template <typename R, typename Arg>
class Callback
{
  public:
  typedef R (*FuncType)(void*, Arg);
  Callback (FuncType f, void* subscription) : f_(f), subscription_(subscription) { }

  R operator()(Arg a)
  {
    (*f_)(subscription_,a);
  }

  private:
  FuncType f_;
  void* subscription_;
};

template <typename R, typename Arg, typename T, R (T::*Func)(Arg)> 
R CallbackWrapper (void* o, Arg a)  
{
  return (static_cast<T*>(o)->*Func)(a);
}

class Pricer 
{
  public:
  typedef Callback<void,unsigned int> CbType;

  void attach ( CbType cb )
  {
    callbacks_.emplace_back(cb);
  }

  void receivePrice ( double price )
  {
    broadcast(static_cast<unsigned int>(price*100));
  }

  void broadcast (unsigned int price)
  {
    for ( auto& i : callbacks_)
    {
      i(price);
    }
  }

  private:
  std::vector<CbType> callbacks_;
};


class Strategy
{
  public:
   Strategy(Pricer*  p) :  p_(p) 
   { 
     p->attach(Callback<void,unsigned int>(&CallbackWrapper<void,unsigned int, Strategy, &Strategy::update>, static_cast<void *>(this)));
   } 

   void update(unsigned int price)
   {
    //update model with price
     std::cout << "Received price: " << price / 100.0 <<  std::endl;
   }

  private:
  Pricer* p_;
};

int main ( int argc, char *argv[])
{
  Pricer p;
  Strategy s(&p);

  p.receivePrice(105.67);
}
\$\endgroup\$
4
  • \$\begingroup\$ Sure. But why would I use this? \$\endgroup\$ Sep 12, 2013 at 5:48
  • \$\begingroup\$ Some of the code wasn't pasted originally. The code shows the usage where you would have any object register it's member function for a call back. See strategy registering to get notified for prices. It would be a generic call back system that would handle member functions and functions with a single Callback/CallbackWrapper class. Also using templates in this way will give runtime information of the types at compile time. \$\endgroup\$
    – bjackfly
    Sep 13, 2013 at 7:56
  • \$\begingroup\$ Also what are your thoughts on the void * and performance in this case otherwise I would have a add a type T to the Callback and have one for each Callback type \$\endgroup\$
    – bjackfly
    Sep 13, 2013 at 8:15
  • \$\begingroup\$ Is this an exercise just so you can practice? Fortunately what you are trying to do is already covered by std::function<>. So this is nothing that you should ever use in real code. \$\endgroup\$ Sep 15, 2013 at 8:21

1 Answer 1

4
\$\begingroup\$

The functionality you are trying to create already exists in std::function<> using std::bind<> to help.

Comments on the rest of the code:

Here you use emplace back:

  void attach ( CbType cb )
  {
    callbacks_.emplace_back(cb);
  }

Emplace back is usually used when you have the arguments and want to build the object in place using the arguments. By passing the object you are going to just invoke the copy constructor. As a result there is no benefit from using it. Though there is nothing wrong with using it either. Currently I am still working out when to use emplace_back() over push_back() but this is one situation where I would still use push_back().

Also because you pass the argument by value you are copying the object into the function then using the copy constructor to put it in the array resulting in another copy. So here I would pass by reference.

  void attach(CbType const& cb)
  {
    callbacks_.push_back(cb);
  }

Don't use unnecessary casts

broadcast(static_cast<unsigned int>(price*100));

// This is easier to read as:

broadcast(price*100); // double is auto converted to unsigned int

Use standard types:

typedef Callback<void,unsigned int> CbType;

// Replace with:

typedef std::function<void(unsigned int)> CbType;

If you use the standard function then the equivalent to creation becomes much simpler

 p.attach(std::bind(&Strategy::update,this, _1));

Don't use pointers where a reference is a better choice:

Strategy(Pricer*  p) :  p_(p) 

You are passing a pointer and not checking for NULL. Actually the code never checks for NULL so you must use a valid pointer. In this case you may as well pass a reference. If you want to store this internally as a pointer then take its address inside your object.

Strategy(Pricer&  p) :  p_(&p)  // p can never be invalid. 
\$\endgroup\$
5
  • \$\begingroup\$ Thanks for the help I need to review how to do this using std::function and std::bind. I was using templates to help with registering the objects at compile versus runtime. \$\endgroup\$
    – bjackfly
    Sep 18, 2013 at 18:02
  • \$\begingroup\$ Do you have some code that uses std::bind and std::function to do equivalent? I think I need to review something like that \$\endgroup\$
    – bjackfly
    Sep 18, 2013 at 18:09
  • \$\begingroup\$ With the emplace_back I thought it would just do a move of the copied argument object in place instead of a copy. Is this not the case? This is the reason why I was calling emplace_back to try and remove the copy \$\endgroup\$
    – bjackfly
    Sep 18, 2013 at 18:12
  • \$\begingroup\$ Also the R CallbackWrapper (void* o, Arg a) is used to hide the member function details to create a generic function pointer can I just get rid of this using std::bind then? I think an example would help me out. \$\endgroup\$
    – bjackfly
    Sep 18, 2013 at 18:16
  • \$\begingroup\$ with regards to my move comment I guess I was thinking it would be a copy elision situation. \$\endgroup\$
    – bjackfly
    Sep 18, 2013 at 18:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.