2

I need to call B::f() in the thread member function. What I get is "pure virtual method called". How can I do this?

I assume all this happens because of &A::thread_f in the A initializer list where I explicitly name a scope.

class A {
protected:
    std::thread _thread;

    A() : _thread(&A::thread_f, this) { }

    ~A() { 
        _thread.join();
    }

    virtual void f() = 0;

    void thread_f() {
        f();
    }
};

class B : public A {
protected:
    void f() override {
        std::cout << "B::f()" << std::endl;
    }
};
Evg
  • 25,259
  • 5
  • 41
  • 83
GeNtlE
  • 23
  • 4
  • It is not safe to call member functions of `B` *before* constructing `B`, and when you're inside `A`'s constructor, `B` has not been constructed yet. – Evg Sep 01 '21 at 07:39
  • I made up this code (minimal reproducible example) from a 3rd party library. I caught a segfault there and now trying to figure out now to make it work. – GeNtlE Sep 01 '21 at 07:48
  • The answer depends on how much freedom you have to change the code. Anyway, you have to ensure that `B` is fully constructed before you call its member functions and is destructred after all member functions call return. In your example both of these conditions are broken. – Evg Sep 01 '21 at 08:10

3 Answers3

2

as @Evg says, by starting a thread,A ctor call member function f of B before constructing B.

The solution is to start thread after B is constructed.

#include<iostream>
#include<vector>
#include <stdlib.h>
#include <thread>

using namespace std;
class A {
public:
    ~A() {
        _thread.join();
    }
    void start()
    {
        _thread = std::thread(&A::thread_f, this);
    }
protected:
    std::thread _thread;
    A()
    {
    }

    virtual void f() = 0;
    void thread_f() {
        f();
    }
};

    class B : public A {
    protected:
        void f() override
        {
            std::cout << "B::f()" << std::endl;
        }
    };

void main() {
    A *pA=new B;
    pA->start();
    delete pA;
}
kenash0625
  • 657
  • 3
  • 8
  • This is also problematic. `B` is destructed *before* `A` is, so the thread could be run and joined *after* `B` has already been destructed. – Evg Sep 01 '21 at 08:02
  • @463035818_is_not_a_number And why is `A::f()` called? I guess because a correct value for `vptr` in `B` has not yet been set because `B` has not yet been constructed. Note that here `f()` is called not in the constructor, but inside the thread. – Evg Sep 01 '21 at 08:15
  • @463035818_is_not_a_number If you [add a short pause](https://godbolt.org/z/rWrq7xqrq) inside `thread_f()` to make sure that `B` is constructed, this code will print `B::f()` as expected. – Evg Sep 01 '21 at 08:20
  • 1
    @Evg fwiw, this is what I used to convince myself that I was wrong: https://godbolt.org/z/6EWvfo4s4. For some reason I expected that `&A::thread_f` does not use virtual dispatch because it appears in the constructor – 463035818_is_not_an_ai Sep 01 '21 at 08:25
  • @Evg he/she is correct.my answer is not good .me found a better answer here go: https://stackoverflow.com/questions/962132/calling-virtual-functions-inside-constructors – kenash0625 Sep 01 '21 at 08:58
  • @kenash0625 There is a difference between calling a virtual function inside a constructor and inside a thread initialized in a constructor. – Evg Sep 01 '21 at 08:59
  • no virtual function calls in ctor/dtor is it the same for ```this``` pointer? I guess this is why @GeNtlE get the error "pure virtual method called". – kenash0625 Sep 01 '21 at 09:08
  • GeNtlE calls a virtual function not from a constructor but from a thread. – Evg Sep 01 '21 at 09:10
2

The reason this doesn't work is due to c++'s order of construction. When a class is constructed, any base classes are constructed before the derived class.

This means that when you call the virtual function f() in your base class, class B has not yet been constructed. This means the only available option is to try and call A's implementation of f(). The problem here is that A doesn't have an implementation of f() and thus the error occurs.

For ways to solve this, take a look here. I would suggest moving your call to f() into a separate initialisation stage that can be called after construction, which will mean both base and derived classes will have been fully constructed by the time of the call.

WalleyM
  • 172
  • 7
1

There are two problems here, both related to the absence of synchronization.

The first problem is that a virtual call could be made on an object that has not yet been fully constructed. A new thread is created before the construction of B, f() is called inside that thread and there is no guarantee that by that time B is fully constructed. The behaviour is undefined.

Going into some implementation details, virtual calls are typically implemented using virtual tables. A pointer to a virtual table is set during B's construction by a compiler, and if you call f() before that pointer is correctly set, you're likely to call A::f() instead of B::f(). Calling A::f() generates the "pure virtual method called" error message. Note that f() is called inside a thread, not inside a constructor, so that the call will be virtual and won't be resolved to A::f() at compile time.

The second problem is that a virtual call could be made on an object that has already been destructed. The destructor of A, in which you join a thread, is called after the destruction of B. Again, making a virtual call on a destructed object is undefined behaviour.

To get some insight into what's going on, you can add two mutexes:

std::mutex m1, m2;

struct A {
    std::thread thread;
    
    A() : thread(&A::thread_f, this) { 
        std::cout << "A()" << std::endl;
    }
    
    ~A() { 
        std::cout << "~A()" << std::endl;
        thread.join();
    }

    virtual void f() = 0;
    
    void thread_f() {
        std::cout << "thread_f()" << std::endl;        
        m1.lock();
        f();
        m2.unlock();
    }
};

struct B : A {
    B() {
        std::cout << "B()" << std::endl;
        m1.unlock();
    }

    ~B() {
        m2.lock();    
        std::cout << "~B()" << std::endl;
    }

    void f() override {
        std::cout << "B::f()" << std::endl;
    }
};

int main() {
    m1.lock();
    m2.lock();
    B b;
}

Now you'll see the expected "B::f()" output. Demo. I would hardly call this a solution, though.

The answer by kenash0625 addresses the first problem, but it doesn't address the second one.

Evg
  • 25,259
  • 5
  • 41
  • 83