简体   繁体   中英

Passing a member function of another class into a std::function parameter

I have a class with a function that takes a std::function and stores it. This part seems to compile ok (but please point out any issue if there are any)

#include <functional>
#include <iostream>

struct worker
{
   std::function<bool(std::string)> m_callback;
   void do_work(std::function<bool(std::string)> callback)
   {
      m_callback = std::bind(callback, std::placeholders::_1);
      callback("hello world\n");
   }
};

// pretty boring class - a cut down of my actual class
struct helper
{
   worker the_worker;
   bool work_callback(std::string str)
   {
      std::cout << str << std::endl;
      return true;
   }
};

int main()
{
   helper the_helper;
   //the_helper.the_worker.do_work(std::bind(&helper::work_callback, the_helper, std::placeholders::_1));  // <---- SEGFAULT (but works in minimal example)
   the_helper.the_worker.do_work(std::bind(&helper::work_callback, &the_helper, std::placeholders::_1));  // <---- SEEMS TO WORK
}

I get a segfault , but I am not sure why. I have used this before, in fact, I copied this example from another place I used it. The only real difference that the member function was part of the class I called it from (ie this instead of the_helper ).

So this is why I am also asking if there is anything else I am doing wrong in general? Like should I be passing the std::function as:

void do_work(std::function<bool(std::string)>&& callback)

or

void do_work(std::function<bool(std::string)>& callback)

As also noted by @Rakete1111 in comments, the problem probably was in this code:

bool work_callback(std::string str)
{
   std::cout << str << std::endl;
}

In C++ if a non-void function does not return a value the result is undefined behavior.

This example will crash with clang but pass with gcc .

If helper::work_callback returns (eg, true ) the code works just fine .

I don't know why your code seg faults because I was spoiled and skipped std::bind straight to lambdas. Since you use C++11 you should really convert your code from std::bind to lambdas:

struct worker
{
   std::function<bool(std::string)> m_callback;

   void do_work(std::function<bool(std::string)> callback)
   {
      m_callback = callback;
      callback("hello world\n");
   }
};

Now with work_callback and calling do_work things need some analysis.

First version:

struct helper
{
   worker the_worker;
   bool work_callback(std::string)
   {
      return false;
   }
};

int main()
{
   helper the_helper;
   the_helper.the_worker.do_work([&](std::string s) { return the_helper.work_callback(s); });
}

Now this version works with your toy example. However out in the wild you need to be careful. The lambda passed to do_work and then stored in the_worker captures the_helper by reference. This means that this code is valid only if the helper object passed as reference to the lambda outlives the worker object that stores the m_callback . In your example the worker object is a sub-object of the the helper class so this is true. However if in your real example this is not the case or you cannot prove this, then you need to capture by value.

First attempt to capture by value (does not compile):

struct helper
{
   worker the_worker;
   bool work_callback(std::string)
   {
      return false;
   }
};

int main()
{
   helper the_helper;
   the_helper.the_worker.do_work([=](std::string s) { return the_helper.work_callback(s); });
}

This does not compile because the copy of the_helper stored in the lambda object is const by default and as such you cannot call work_callback on it.

A questionable solution if you can't make work_callback const is to make the lambda mutable :

struct helper
{
   worker the_worker;
   bool work_callback(std::string)
   {
      return false;
   }
};

int main()
{
   helper the_helper;
   the_helper.the_worker.do_work([=](std::string s) mutable { return the_helper.work_callback(s); });
}

But you need to think if this is what you intended.

What would make more sense is to make work_callback const:

struct helper
{
   worker the_worker;
   bool work_callback(std::string) const
   {
      return false;
   }
};

int main()
{
   helper the_helper;
   the_helper.the_worker.do_work([=](std::string s) { return the_helper.work_callback(s); });
}

The reason for getting SEGFAULT has been already mentioned in the comments.

However, I would like to point out that, you need to use neither std::bind nor std::function , here in your given case. Instead, simply having a lambda and a function pointer you can handle what you intend to do.

struct worker
{
    typedef bool(*fPtr)(const std::string&); // define fun ptr type 
    fPtr m_callback;
    void do_work(const std::string& str)
    {
        // define a lambda
        m_callback = [](const std::string& str)
        { 
            /* do something with string*/ 
            std::cout << "Call from worker: " << str << "\n";
            return true;
        }; 
        bool flag = m_callback(str);// just call the lambda here
        /* do some other stuff*/ 
    }
};

struct helper 
{
    worker the_worker;
    bool work_callback(const std::string& str)
    {
        std::cout << "Call from helper: ";
        this->the_worker.do_work(str);
        return true; ------------------------>// remmeber to keep the promise
    }
};

And use case would be:

int main()
{
    helper the_helper;
    the_helper.work_callback(std::string("hello world"));

    // or if you intend to use

    the_helper.the_worker.do_work(std::string("hello world"));

    return 0;
}

see Output here :


PS : In the above case, if worker does not required m_callback for later cases(ie, only for do_work() ), then you can remove this member, as lambdas can be created and called at same place where it has been declared.

struct worker
{
    void do_work(const std::string& str)
    {
        bool flag = [](const std::string& str)->bool
        { 
            /* do something with string*/ 
            std::cout << "Call from worker: " << str << "\n";
            return true; 
        }(str); -------------------------------------> // function call
        /* do other stuff */
    }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM