"pure virtual method called" when implementing a boost::thread wrapper interface
Asked Answered
M

2

6

I have a small wrapper which centralize what's relative to threads :

class Thread {
protected:
    boost::thread *m_thread;

    virtual void work() = 0;

    void do_work() {
        work();
    }

public:
    Thread() : m_thread(NULL) {}
    virtual ~Thread() {
        catch_up();
        delete m_thread;
    }

    inline void catch_up() {
        if(m_thread != NULL) {
            m_thread->join();
        }
    }

    void run() {
        m_thread = new boost::thread(boost::bind(&Thread::do_work, boost::ref(*this)));
    }
};

When I implement it, say with the following :

class A : public Thread {
    void work() {}
};

At :

A a; a.run();

I got a runtime termination with a pretty "pure virtual method called" displayed. I think it's the boost::bind argument, but I don't know how to say "Use virtual pure implementation"...

Thanks aforehand.

Regards,

Mister Mystère

Mattock answered 1/7, 2010 at 17:59 Comment(0)
G
6

Your crash happens only when your program exits immediately: it calls class A's destructor which finishes and calls Thread's destructor before the newly started thread had a chance to be scheduled. The thread then calls your virtual function, but class A no longer exists, so it attemps to call Thread's do_work(), which calls the pure virtual work(). Here's your program with extra outputs:

run() started 
run() ended
~A() started
~A() ended
~Thread() started
catch_up() started
do_work() started
pure virtual method called

Standard-wise, I think this is undefined behavior because the object's lifetime has already ended (destructor call began) when a reference to it (boost::ref(*this)) was used to call do_work() from the thread.

Solution: let your thread execute before you destruct your object:

A a; a.run();
a.catch_up();

Or, as boost.thread documentation says, "the user of Boost.Thread must ensure that the referred-to object outlives the newly-created thread of execution."

Global answered 1/7, 2010 at 19:2 Comment(1)
Well, that was simple... Thanks, you made it clear. But now I have a greater problem (random booleans apparently...), and I think it will be wayyy more difficult to debug. I just shouldn't have used my "joker" here for that, otherwise I'll seem needy ^^'Ideogram
W
1

I'm going out on a limb here, but I suspect the problem is with your Thread destructor:

virtual ~Thread() { 
    catch_up(); 
    delete m_thread; 
} 

If the thread hasn't started yet, calling catch_up() in the destructor will start the boost thread using Thread's vtable rather than A's, as in C++ at the point of the destructor the vtable matches the scope of the type of the destructor, not the most derived vtable.

Wavy answered 1/7, 2010 at 18:7 Comment(3)
I think new allocates the thread handle after creating it right ? Thus, checking in catch_up() if m_thread equals NULL suffice to protect it in case the destructor is faster than the creation of the thread.Ideogram
I'm referring to Thread's destructor, not boost::thread's destructor.Wavy
I know, I was talking about that too. But now that I understand, I think you pointed it out too, sorry for having misunderstood, my bad ;)Ideogram

© 2022 - 2024 — McMap. All rights reserved.