Order of destruction for objects and static storage duration


#1

File::nonexistent is a member of File with static storage duration.

File is also a leak-detected object, via JUCE_LEAK_DETECTOR.

If the leak detector counters are also objects with static storage duration, then how can we be assured that the File::nonexistent object will be destroyed before the corresponding leak detector counter? We can’t. I’m getting an assert about a leaked File object, and it’s the File::nonexistent member.

I warned about this problem a while ago, and Jules never acknowledged it. Now it is biting me in the buttocks!

Help!


#2

Anyone?


#3

Yeah, it’s a tough one… I guess that there’s really no reason for File to be leak-checked, as nobody would be silly enough to create a File object on the heap (…or would they…?)


#4

Here’s what I did, I made File::nonexistent a function instead of a global. Then I used my SharedSingleton to initialize it. These are reference counted singletons that are dynamically allocated and all “hang” off a single static object (managed by ONE global called PerformedAtExit):

// We need to make a shared singleton or else there are
// issues with the leak detector and order of destruction.
//
class NonexistentHolder : public SharedSingleton <NonexistentHolder>
{
public:
    NonexistentHolder ()
        : SharedSingleton <NonexistentHolder> (SingletonLifetime::persistAfterCreation)
    {
    }

    static NonexistentHolder* createInstance ()
    {
        return new NonexistentHolder;
    }

    File const file;
};

File const& File::nonexistent ()
{
    return NonexistentHolder::getInstance ()->file;
}

This utilizes all the things that I have talked about over the last 2 years regarding objects with static storage duration, zero-initialization of static objects without constructors, and the concepts of reference counted singletons.

Ultimately the solution comes down to:

  • Never create objects with static storage duration anywhere in JUCE, Beast, or client code
  • Always use SharedSingleton for global variables

This especially applies to function level objects with static storage duration, such as in getCurrentThreadHolder():

static CurrentThreadHolder::Ptr getCurrentThreadHolder()
{
    static CurrentThreadHolder::Ptr currentThreadHolder; // BAD!!!

VFLib has the suite of tools of dealing with this issue, if you remember I posted a utility class “StaticObject”. It tidys up constructs like this:

Old:

static char currentThreadHolderLock [sizeof (SpinLock)]; // (statically initialised to zeros).

With StaticObject

static StaticObject <SpinLock> currentThreadHolderLock;

Here’s the old post:

Generic StaticObject, any static var can be thread safe!

This is exactly why I changed the prefix from juce_, I’ve been methodically rewriting the entire forked juce_core module to use these techniques so that the leak checking and what not actually works right.

I highly doubt you want to adopt ALL of these changes just to fix order of construction and destruction issues right (and make those solutions available to JUCE users)… or do you?


#5

Yeah, I didn’t want it to have to use function calls though - I like the syntax of just using File::nonexistent as an object.

Another approach would be to use a special class for File::nonexistent, and give it an operator that will convert it to a File. The custom class could happily be a static object since it wouldn’t need to actually contain any data, and the compiler would probably optimise any uses of File::nonexistent down to a call to the default File() constructor.


#6

That’s pretty clever, and it solves the problem for File but the ordering issue will just pop up in another place eventually - I always have to have JUCE leak checking turned off in any of my projects that use VFLib. There’s tons of objects with static storage duration in JUCE.