My codebase contains this very simple templated function that gets called from a lot of different places:
// Returns a read-only reference to a default-constructed
// object of the specified type
template <typename T> const T & GetDefaultObjectForType()
{
static const T _defaultObject{};
return _defaultObject;
}
This function is useful in templated code, since it allows the templated code to access a default-constructed object of the type it is templated on, without having to construct one for that purpose.
It works nicely, but the problem I ran into today is that helgrind is telling me there is a race condition when I have multiple threads that call this function; that is because g++'s underlying implementation of the function apparently looks something more like this (pseudocode):
template <typename T> const T & GetDefaultObjectForType()
{
static char _defaultObject[sizeof(T)];
static std::atomic<bool> _isConstructed = false;
if (_isConstructed == false)
{
_isConstructed = true;
new (_defaultObject) T(); // demand-construct the object!
}
return reinterpret_cast<const T &>(_defaultObject);
}
... so because of the above, helgrind
tells me that thread A's call to GetDefaultObjectForType()
has written to the object (i.e. to demand-construct it) while thread B's code immediately after its call to GetDefaultObjectForType()
has read from the object, and therefore there is a race condition.
I'm not sure if this is a real, will-bite-me-in-the-butt-someday race condition or just a false-positive caused by helgrind being too sensitive, but in either case, my question is the same: Is there some technique I can use to force the construction of the object to happen at (or near) the top-of-main, i.e. before any threads have been spawned, so that the singleton-object can be truly read-only as far as all the threads that call this function are concerned?
Obviously I could manually call GetDefaultObjectForType<EveryTypeIUse>()
at the top of main()
, but that would be a real maintenance hassle since I use so many different types of object that I'd be likely to forget to include some of the types (and it would force my main.cpp
to have to #include
a lot of header files that I'd prefer it not have to include).
Aucun commentaire:
Enregistrer un commentaire