0

So my problem starts pretty much like this question:
Start thread with member function

I have some class Foo, that looks like this:

struct Foo
{
    int y;
    thread t;

    void set(int x){
       y = x;
    }

    void plan(int x){
        thread = std::thread ([&]{
        set(x);
    });

    void get(){
        if (t.joinable())
            t.join();
    }
};

Other answers also suggest:

    void plan(int x){
        thread = std::thread(&Foo::set, this, x);
    };

Now, I want to use Foo as a base class for various child class with overloaded set() functions, for exemple:

struct Bar: public Foo
{
    void set(int x){
       y = x*2;
    }
}

My problem is that if done that way, Bar::plan() result in Foo::set() being runned in the new thread instead of Bar::set as expected.

Is there another solution than having to write again the plan() method in every child class of Foo?

Oreille
  • 375
  • 3
  • 11

3 Answers3

3

You might simply make set virtual:

struct Foo
{
    // ...
    virtual void set(int x);
};

struct Bar : Foo
{
    void set(int x) override;
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302
2

Just mark set as virtual and use the lambda version, being careful to capture everything by value, as the plan invocation may (and probably will) return before the thread actually start to run.

struct Foo
{
    int y;
    thread t;

    virtual void set(int x){
       y = x;
    }

    void plan(int x){
        t = std::thread ([this, x]{
            this->set(x);
        });
    }

    void get(){
        if (t.joinable())
            t.join();
    }
};

this will call the correct set version even when plan is invoked in a derived class.

Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
  • I think it is not necessary to use lambda in plan(). `std::bind` should do the job here. – Kunal Puri Dec 27 '18 at 23:42
  • @KunalPuri: why risk? Lambdas rules are simpler and clearer, and it's immediately clear what they do. `std::bind` and its rotten kin? Not much. I wouldn't bet anything over whether it does the right thing (whatever it is) or not in regard to virtual methods, and most programmers I know, me included, [would have to look it up on a reference](https://en.cppreference.com/w/cpp/utility/functional/bind) - if you can understand what happens it behind the forest of `std::decay::whatever`. Personally I wasted 30 seconds looking for details about `virtual` and then gave up. – Matteo Italia Dec 27 '18 at 23:48
  • It was that simple. I'm not sure however how to use std::bind here though? edit: nevermind. – Oreille Dec 27 '18 at 23:49
  • @KunalPuri Wrong way round. It is not necessary to use `std::bind` in `plan()` because a lambda will do the job here. – Lightness Races in Orbit Dec 28 '18 at 01:27
  • @LightnessRacesinOrbit Actually, Both are not required in this case. See my answer. – Kunal Puri Dec 28 '18 at 01:42
  • @KunalPuri As was covered in comments under your answer a few hours ago, that solution is basically `std::bind` – Lightness Races in Orbit Dec 28 '18 at 02:07
1

Though the answer has been accepted already, Here is a way to do the same using std::bind.

#include <iostream>
#include <thread>
#include <functional>

using namespace std;

struct Foo
{
    int y;
    thread t;

    virtual void set(int x){
       y = x;
    }

    void plan(int x){
        t = std::thread (std::bind(&Foo::set, this, x));
    }

    void get(){
        if (t.joinable())
            t.join();
    }
};

struct Bar: public Foo
{
    void set(int x){
        cout << "Called" << endl;
       y = x*2;
    }
};

int main() {
    Bar b;
    b.plan(2);
    b.get();

    return 0;
}

Also, Without using lambda, you could have done this also:

void plan(int x){
    t = std::thread (&Foo::set, this, x);
}
Kunal Puri
  • 3,419
  • 1
  • 10
  • 22
  • 1
    `std::bind` here just adds yet another wrapping layer for what is already available through the `std::thread` constructor (both mostly just defer the hard lifting to the `std::invoke` machinery) – Matteo Italia Dec 28 '18 at 00:06
  • 1
    Thank you for showing how to use std::bind in that context. However your solution without lambda gave me: "error: cannot create a non-constant pointer to member function". – Oreille Dec 28 '18 at 00:07
  • 1
    `&this->set` should be `&Foo::set`. – Jarod42 Dec 28 '18 at 00:09
  • For me, `&this->set` is working. @Jarod42 `&Foo::set` could be used also. Thanks for the suggestion. – Kunal Puri Dec 28 '18 at 00:20
  • 2
    @KunalPuri: `&this->set` is invalid: [Demo for bind](http://coliru.stacked-crooked.com/a/2849bcc0527b9050) and [Demo](http://coliru.stacked-crooked.com/a/e6ca0f4355f0b756) – Jarod42 Dec 28 '18 at 00:26
  • I agree with your point @MatteoItalia. That's why I included both. Because OP wanted to know how to use `std::bind` in this case. – Kunal Puri Dec 28 '18 at 00:26
  • @Jarod42 I am using `i686-w64-mingw32-g++.exe` and it works fine for me. – Kunal Puri Dec 28 '18 at 00:30
  • @Jarod42 I think ISO C++ forbids it. I don't know how it is working on `i686-w64-mingw32-g++.exe` 7.1.0. – Kunal Puri Dec 28 '18 at 00:34