39

I have a void function inside of a class. In old C++ i'd make a function static taking the class name as a parameter and had my own class which took a static void function + a void* for me to easily call it.

However that feels old school. It also isn't templated which feels like i could be doing more. What is a more modern way of creating callbacks to myclassVar.voidReturnVoidParamFunc

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • "It also isn't templated which feels like i could be doing more." C++ code doesn't have to be templated to be C++. – Nicol Bolas Sep 09 '12 at 14:48
  • @NicolBolas to be fair this solution did use one and works *really* well (lambdas) –  Sep 09 '12 at 14:56

2 Answers2

68

Use std::function and lambdas (or std::bind()) to store callables:

#include <functional>
#include <iostream>


class Test
{
public:
      void blah() { std::cout << "BLAH!" << std::endl; }
};

class Bim
{
public:
      void operator()(){ std::cout << "BIM!" << std::endl; }
};

void boum() { std::cout << "BOUM!" << std::endl; }


int main()
{
    // store the member function of an object:
    Test test;  
    std::function< void() > callback = std::bind( &Test::blah, test );
    callback();

    // store a callable object (by copy)
    callback = Bim{};
    callback();

    // store the address of a static function
    callback = &boum;
    callback();

    // store a copy of a lambda (that is a callable object)
    callback = [&]{ test.blah(); }; // often clearer -and not more expensive- than std::bind()
    callback();
}      

Result:

BLAH!

BIM!

BOUM!

BLAH!

Compiles and run: http://ideone.com/T6wVp

std::function can be used as any copyiable object, so feel free to store it somewhere as a callback, like in object's member. It also means that you can freely put it in standard containers, like std::vector< std::function< void () > > .

Also note that equivalent boost::function and boost::bind have been available for years.

Community
  • 1
  • 1
Klaim
  • 67,274
  • 36
  • 133
  • 188
  • 1
    @Hauleth I didn't say it's complicated, I said it clearer with a lambda. Remembering arguments order (in particular when you get obscure compilation error if it's the wrong order) makes it "harder" than just writing the call explicitly like in the lambda. – Klaim Sep 09 '12 at 12:11
  • I know you posted this a long time ago but I need some clarification here: the first 3 lines in main where you bind the class instance as argument to the class function itself works because the compiler would does this as well secretly when we normally call a class member function, right? So the compiler adds a reference to the instance as *this* argument to each function as first parameter! Can you confirm my assumption? – binaryguy Sep 22 '17 at 16:52
  • There is a problem with the bind in your code, it should take a pointer like `std::function< void() > callback = std::bind( &Test::blah, &test );` – User55412 Jan 26 '18 at 10:29
  • 1
    @dynamode Both are valid, but in the example there is a copy of `test` put inside the `callback` object, while in your version it would be a pointer put inside `callback`. As expected both work as long as the pointer and/or the object are valid at the call time. – Klaim Feb 05 '18 at 20:25
  • @binaryguy It's not the compiler guessing stuffs, it's `std::bind` implementation/definition (it's part of why it's complicated code that generate weird errors when you pass something wrong). To be short, it detects that you are giving a member function, in which case it expect the second argument to be either a pointer to the object of the related type OR a `std::reference_wrapper` or an object to copy in. Here there is a copy of the `test` object taken by bind. If you want to pass a reference, you have to use `std::ref` or `std::cref`. You can also pass a pointer like @dynamode pointed. – Klaim Feb 05 '18 at 20:29
8

For an example of passing in parameters to a C++ 11 callback using Lambda's and a vector, see http://ideone.com/tcBCeO or below:

class Test
{
public:
      Test (int testType) : m_testType(testType) {};
      void blah() { std::cout << "BLAH! " << m_testType << std::endl; }
      void blahWithParmeter(std::string p) { std::cout << "BLAH1! Parameter=" << p << std::endl; }
      void blahWithParmeter2(std::string p) { std::cout << "BLAH2! Parameter=" << p << std::endl; }

      private:
         int m_testType;

};

class Bim
{
public:
      void operator()(){ std::cout << "BIM!" << std::endl; }
};

void boum() { std::cout << "BOUM!" << std::endl; }


int main()
{
    // store the member function of an object:
    Test test(7);  
    //std::function< void() > callback = std::bind( &Test::blah, test );
    std::function< void() > callback = std::bind( &Test::blah, test );
    callback();

    // store a callable object (by copy)
    callback = Bim{};
    callback();

    // store the address of a static function
    callback = &boum;
    callback();

    // store a copy of a lambda (that is a callable object)
    callback = [&]{ test.blah(); }; // might be clearer than calling std::bind()
    callback();

    // example of callback with parameter using a vector
    typedef std::function<void(std::string&)> TstringCallback;

    std::vector <TstringCallback> callbackListStringParms;
    callbackListStringParms.push_back( [&] (const std::string& tag) {     test.blahWithParmeter(tag); }); 
    callbackListStringParms.push_back( [&] (const std::string& tag) { test.blahWithParmeter2(tag); }); 

    std::string parm1 = "parm1";
    std::string parm2 = "parm2";
    int i = 0;
    for (auto cb : callbackListStringParms ) 
    {
        ++i;
        if (i == 1)
            cb(parm1);
        else
            cb(parm2);

    }
}      
Romano Zumbé
  • 7,893
  • 4
  • 33
  • 55
LeslieM
  • 2,105
  • 1
  • 17
  • 8