Boost::thread - problems with inheritance

Started by
10 comments, last by GoldenArm 10 years, 8 months ago
I have made a class to launch threads like this:

#include <boost/thread.hpp>class BaseThreadObject{	protected://---------- Protected Properties -----------------		boost::thread* m_thread;//------------ Protected Methods ------------------	public://-- Constructors, Destructor & Support Methods ---		BaseThreadObject();		virtual ~BaseThreadObject();		void Startup();		void Shutdown();		//-------- Get's --------------------------		//-------- Set's --------------------------//---------- Public Methods -----------------------		void operator()();		virtual void ThreadMain() = 0;};// ------------------------------------------------// ------------------------------------------------// ------------------------------------------------void BaseThreadObject::operator()(){	ThreadMain();}// ------------------------------------------------// ------------------------------------------------// ------------------------------------------------void BaseThreadObject::Startup(){	m_thread = new boost::thread(boost::ref(*this));//	m_thread = new boost::thread(*this);}// ------------------------------------------------// ------------------------------------------------// ------------------------------------------------void BaseThreadObject::Shutdown(){	m_thread->join();}// ------------------------------------------------// ------------------------------------------------// ------------------------------------------------void BaseThreadObject::ThreadMain(){	// whatever}


The Startup launches the thread with the class as a ref, to prevent it from being copied (boost takes 10(!!) copies when launching). Also, I hoped that with a ref, that I could have preserved the inheritance, so I could call derived classes through virtual methods.

However, when I call a virtual method, even if it is implemented in the base class, like the operator()() calls ThreadMain() (which I hoped I could then freely derive for new classes, while preserving basic threading functionality in this baseclass alone), it will fail with this message:
"Unhandled exception at 0x00000000 in [prog-name].exe: 0xC0000005: Access violation reading location 0x00000000"

I have similar problems when I don't use the ref and allows it to copy. It will only copy the class, that is directly inputted, not the classes it derives from.

Is there any way I can create a class like this with boost, that retains an inheritance structure?
Quote:CalvinI am only polite because I don't know enough foul languageQuote:Original post by superpigI think the reason your rating has dropped so much, Mercenarey, is that you come across as an arrogant asshole.
Advertisement
I don't know why you're having type problems, but what I do is have the class contain a "work()" function.

Then go;

typedef shared_ptr<boost::thread> ThrP;
ThrP thr;

thr=ThrP(new boost::thread(boost::bind(&CMyClass::work,this)));

I don't care then if boost starts copying the bind output about the place, because it's copying a dinky class and not my class.
I'm suspecting that no matter how much you try to cheat by using boost::ref to pass a functor to boost::thread's constructor, internally it will still at some point make a copy of the functor, and when that happens, it won't have a clue that you're really passing a derived thread object and not a BaseThreadObject. Hence it will store a copy of the BaseThreadObject part of the class instance you're passing it. The result -> slicing (google it).


I suggest the following.


struct MyFunctor{	BaseThreadObject* m_ptrToThreadObject;	MyFunctor( BaseThreadObject* ptrToThreadObject ) : m_ptrToThreadObject( ptrToThreadObject ) {}		void operator () ()	{		m_ptrToThreadObject->ThreadMain();	}};void BaseThreadObject::Startup(){	m_thread = new boost::thread( MyFunctor( this ) );}


But beware that you may run into object life time issues if your BaseThreadObject instance gets deleted while your thread is still running (and hence potentially accessing your BaseThreadObject instance).
Red Ant, good suggestion.

I tried it, and put a breakpoint in MyFunctor::operator()(), to inspect the values. m_ptrToThreadObject had the same address as the BaseThreadObject, but the values inside BaseThreadObject were wrong (uninitialized). The same was true when another breakpoint stopped in BaseThreadObject::ThreadMain() (which I implemented directly in BaseThreadObject, to not have inheritance make it even more complex).

How is this possible? Since there is only one class, how can it be sliced?
Im completely lost at what is going on with boost::thread...

Edit:
Just for the record, I created a test variable (an integer) and gave it a value in Startup(), before launching. It is this value that does not show up in the threaded version.
Quote:CalvinI am only polite because I don't know enough foul languageQuote:Original post by superpigI think the reason your rating has dropped so much, Mercenarey, is that you come across as an arrogant asshole.
I've just skimmed over, but wouldn't
new boost::thread(boost::bind(&BaseThreadObject::operator(), this));

be a safe way to address all your issues?

*edit*
And just out of curiosity, why are you storing the boost::thread instance on the heap? You don't need to initialize a boost::thread in its constructor, so the following will work as well (and you don't have to call delete in your destructor):
class Whatever{    boost::thread   m_thread;    Whatever()    {        // some code        m_thread = boost::thread(boost::bind(&Whatever::operator(), this));    }};


Another thing that bugs me (although this may never be an issue in real cases) is that what if operator() gets called BEFORE the derrived class is constructed. I am not sure but I think the overloaded thread main function pointer will not be written into the vtable until the construction of the derrived class. Therefore your code might not work in those cases.

*edit2*
I just saw this got suggested before. If this doesn't work for you, then the issue is something else. We use boost::thread at work in exactly that way and it works perfectly.
I don't know what happened yesterday, but when I tested the code again today with a fresh machine and compilation, it worked as it is supposed to, with Red Ant's implementation.

So now it works perfectly!

SiS-Shadowman, Bind() is a possibility as well, if you don't like the operator()(), and creating thread on the heap is also a possibility. Personally I prefer to create it dynamically, I feel I have more control over it, as I can call Shutdown() and have it destroyed, instead of relying on the destructor (maybe I want to reuse the object and use Shutdown() to reset the object for a new Startup()?).

I have tested it with inheritance as well, and it works fine.

Final implementation:
// ------------------------------------------------// ------------------------------------------------class BaseThreadObject{	// ----------------------------------------	// Red Ant's version modified to my code	// standard. Inlined as implementation is	// irrelevant to outsiders.	// ----------------------------------------	struct BaseThreadObjectFunctor	{		BaseThreadObject* m_basethreadobject;		BaseThreadObjectFunctor(BaseThreadObject* a_basethreadobject);				void operator()();	};	protected://------------ Protected Properties ---------------		int m_testdata;		boost::thread* m_thread;		MyFunctor* m_myfunctor;//------------ Protected Methods ------------------	public://-- Constructors, Destructor & Support Methods ---		BaseThreadObject();		virtual ~BaseThreadObject();		void Startup();		void Shutdown();		//---- Get's ----------------------		//---- Set's ----------------------//-------------- Public Methods -------------------		virtual void ThreadMain();};// ------------------------------------------------// ------------------------------------------------class TestThreadObject : public BaseThreadObject{	protected://------------ Protected Properties ---------------//------------ Protected Methods ------------------	public://-- Constructors, Destructor & Support Methods ---		TestThreadObject();		virtual ~TestThreadObject();		//---- Get's ----------------------		//---- Set's ----------------------//-------------- Public Methods -------------------		virtual void ThreadMain();};// ------------------------------------------------// ------------------------------------------------// ------------------------------------------------BaseThreadObject::BaseThreadObjectFunctor::BaseThreadObjectFunctor(BaseThreadObject* a_basethreadobject) : m_basethreadobject(a_basethreadobject){}// ------------------------------------------------// ------------------------------------------------// ------------------------------------------------void BaseThreadObject::BaseThreadObjectFunctor::operator()(){	m_basethreadobject->ThreadMain();}// ------------------------------------------------// ------------------------------------------------// ------------------------------------------------void BaseThreadObject::Startup(){	m_testdata = 11;	m_myfunctor = new MyFunctor(this);	m_thread = new boost::thread(boost::ref(*m_myfunctor));}// ------------------------------------------------// ------------------------------------------------// ------------------------------------------------void BaseThreadObject::Shutdown(){	m_thread->join();}// ------------------------------------------------// ------------------------------------------------// ------------------------------------------------void BaseThreadObject::ThreadMain(){	// m_testdata will correctly be 11 in the thrown thread	int dd = m_testdata;}// ------------------------------------------------// ------------------------------------------------// ------------------------------------------------void TestThreadObject::ThreadMain(){	// m_testdata will correctly be 11 in the thrown thread - also here	// in the derived thread	int dd = BaseThreadObject::m_testdata;}


[Edited by - Mercenarey on June 25, 2010 9:19:56 AM]
Quote:CalvinI am only polite because I don't know enough foul languageQuote:Original post by superpigI think the reason your rating has dropped so much, Mercenarey, is that you come across as an arrogant asshole.
Quote:Original post by SiS-Shadowman

*edit*
And just out of curiosity, why are you storing the boost::thread instance on the heap? You don't need to initialize a boost::thread in its constructor, so the following will work as well (and you don't have to call delete in your destructor):
class Whatever{    boost::thread   m_thread;    Whatever()    {        // some code        m_thread = boost::thread(boost::bind(&Whatever::operator(), this));    }};



Does that actually work? Does boost::thread really provide the proper assigment / copying semantics for that to work?
Yes. As far as I know this does a simple swap.
Documentation

I stumpled upon this by accident when I created the thread in an initializer list, and it started modifying my class before the constructor was finished.
Thanks alot for the help guys. This has been hands-down my most severe problem to date. Took me two full days of work to finally crack.

GameDev to the rescue every time :)
Quote:CalvinI am only polite because I don't know enough foul languageQuote:Original post by superpigI think the reason your rating has dropped so much, Mercenarey, is that you come across as an arrogant asshole.
Quote:Original post by SiS-Shadowman
Yes. As far as I know this does a simple swap.
Documentation


Well, that's why I'm asking. I've looked at the documentation and didn't find anything in there that says operator = invokes the swap() method. If it really does, then it appears to be an undocumented feature, which means it's probably best not to rely on it.

This topic is closed to new replies.

Advertisement