In C++
, during a class constructor, I started a new thread with this
pointer as a parameter which will be used in the thread extensively (say, calling member functions). Is that a bad thing to do? Why and what are the consequences?
My thread start process is at the end of the constructor.
I'd say that, as a general rule, you should avoid doing this. But you can certainly get away with it in many circumstances. I think there are basically two things that can go wrong:
Generally speaking, if you have complex, error-prone initialization to perform, then it's best to do it in a method rather than the constructor.
Basically, what you need is two-phase construction: You want to start your thread only after the object is fully constructed. John Dibling answered a similar (not a duplicate) question yesterday exhaustively discussing two-phase construction. You might want to have a look at it.
Note, however, that this still leaves the problem that the thread might be started before a derived class' constructor is done. (Derived classes' constructors are called after those of their base classes.)
So in the end the safest thing is probably to manually start the thread:
Main consequence is that the thread might start running (and using your pointer) before the constructor has completed, so the object may not be in a defined/usable state. Likewise, depending how the thread is stopped it might continue running after the destructor has started and so the object again may not be in a usable state.
This is especially problematic if your class is a base class, since the derived class constructor won't even start running until after your constructor exits, and the derived class destructor will have completed before yours starts. Also, virtual function calls don't do what you might think before derived classes are constructed and after they're destructed: virtual calls "ignore" classes whose part of the object doesn't exist.
Example:
Now if you create a DerivedThread, it's a best a race between the thread that constructs it and the new thread, to determine which version of
id()
gets called. It could be that something worse can happen, you'd need to look quite closely at your threading API and compiler.The usual way to not have to worry about this is just to give your thread class a
start()
function, which the user calls after constructing it.Some people feel you should not use the
this
pointer in a constructor because the object is not fully formed yet. However you can use this in the constructor (in the{body} and even in the initialization list) if you are careful.Here is something that always works: the
{body}
of a constructor (or a function called from the constructor) can reliably access the data members declared in a base class and/or the data members declared in the constructor's own class. This is because all those data members are guaranteed to have been fully constructed by the time the constructor's {body} starts executing.Here is something that never works: the {body} of a constructor (or a function called from the constructor) cannot get down to a derived class by calling a virtualmember function that is overridden in the derived class. If your goal was to get to the overridden function in the derived class, you won't get what you want. Note that you won't get to the override in the derived class independent of how you call the virtual member function: explicitly using the this pointer (e.g., this->method()), implicitly using the this pointer (e.g., method()), or even calling some other function that calls the virtual member function on your this object. The bottom line is this: even if the caller is constructing an object of a derived class, during the constructor of the base class, your object is not yet of that derived class. You have been warned.
Here is something that sometimes works: if you pass any of the data members in this object to another data member's initializer, you must make sure that the other data member has already been initialized. The good news is that you can determine whether the other data member has (or has not) been initialized using some straightforward language rules that are independent of the particular compiler you're using. The bad news is that you have to know those language rules (e.g., base class sub-objects are initialized first (look up the order if you have multiple and/or virtual inheritance!), then data members defined in the class are initialized in the order in which they appear in the class declaration). If you don't know these rules, then don't pass any data member from the this object (regardless of whether or not you explicitly use the thiskeyword) to any other data member's initializer! And if you do know the rules, please be careful.
It's fine, as long as you can start using that pointer right away. If you require the rest of the constructor to complete initialization before the new thread can use the pointer, then you need to do some synchronization.
Depends on what you do after starting the thread. If you perform initialization work after the thread has started, then it could use data that is not properly initialized.
You can reduce the risks by using a factory method that first creates an object, then starts the thread.
But I think the greatest flaw in the design is that, for me at least, a constructor that does more than "construction" seems quite confusing.