Using a variant in an stl container initialised at global scope crashes

Minimal reproducible example: if I place the following code at global scope, it crashes with the call stack shown (below).

std::vector<var> foo = {0};

The crash is here:

var::var (const var& valueToCopy)  : type (valueToCopy.type)
{
    type->createCopy (value, valueToCopy.value);
}

Same problem for std::map. It’s fine if I initialise the vector inside main(). Is this a bug or have I misunderstood something?

Jamie

#0	0x000000010013d524 in juce::var::var(juce::var const&) at /Users/jamie/Desktop/NewProject/Builds/MacOSX/../../../../Documents/JUCE/modules/juce_core/containers/juce_Variant.cpp:449
#1	0x000000010013996d in juce::var::var(juce::var const&) at /Users/jamie/Desktop/NewProject/Builds/MacOSX/../../../../Documents/JUCE/modules/juce_core/containers/juce_Variant.cpp:448
#2	0x0000000100002078 in void std::__1::allocator<juce::var>::construct<juce::var, juce::var const&>(juce::var*, juce::var const&&&) [inlined] at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:1752
#3	0x0000000100002063 in void std::__1::allocator_traits<std::__1::allocator<juce::var> >::__construct<juce::var, juce::var const&>(std::__1::integral_constant<bool, true>, std::__1::allocator<juce::var>&, juce::var*, juce::var const&&&) [inlined] at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:1668
#4	0x0000000100002043 in void std::__1::allocator_traits<std::__1::allocator<juce::var> >::construct<juce::var, juce::var const&>(std::__1::allocator<juce::var>&, juce::var*, juce::var const&&&) [inlined] at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:1514
#5	0x0000000100002023 in void std::__1::allocator_traits<std::__1::allocator<juce::var> >::__construct_range_forward<juce::var const*, juce::var*>(std::__1::allocator<juce::var>&, juce::var const*, juce::var const*, juce::var*&) [inlined] at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:1598
#6	0x0000000100001fec in std::__1::enable_if<__is_forward_iterator<juce::var const*>::value, void>::type std::__1::vector<juce::var, std::__1::allocator<juce::var> >::__construct_at_end<juce::var const*>(juce::var const*, juce::var const*, unsigned long) at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/vector:1024
#7	0x0000000100002543 in std::__1::vector<juce::var, std::__1::allocator<juce::var> >::vector(std::initializer_list<juce::var>) [inlined] at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/vector:1285
#8	0x000000010000243e in std::__1::vector<juce::var, std::__1::allocator<juce::var> >::vector(std::initializer_list<juce::var>) [inlined] at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/vector:1278
#9	0x0000000100002414 in ::__cxx_global_var_init.1() at /Users/jamie/Desktop/NewProject/Source/Main.cpp:19
#10	0x00000001000026be in _GLOBAL__sub_I_Main.cpp ()

Yup, That’s because in C++ the order of initialisation of static variables is undefined, and var itself has some internal static variables which must have been created before it can be safely used.

So the take-home message is: Don’t use var (or any complex type, really!) in file-scope statics. You’re probably OK with them in function-scoped statics though.

1 Like

I’m glad I found this thread–I’ve been bashing my head against this problem for the past hour.

I have a static const member variable of var, like this:

class SomeClass
{
    static const std::vector<var> m_vars;
}

but the normal way of initializing it in global namespace is not working:

const std::vector<var> SomeClass::m_vars { var("text"), var(2.0f) };

What is the best solution? Should I just forego the const and initialize it on site? Or is there some other workaround?

This is known as the static initialisation order fiasco, and stems from initialisation order of static variables between compilation units being undefined. Putting the static at class scope as you have done won’t help because it doesn’t impose any further constraints on initialisation order. What you need to do is place the static inside a function (or method) as @jules suggested, therefore forcing the static to be initialised when the function is first called. This is sometimes called lazy initialisation.

So you could write:

class SomeClass
{
       const std::vector<var>& getVars()
       {
               static const std::vector<var> m_vars;
               return m_vars;
       }
}

Then access the static later with:

const auto& vars = SomeClass().getVars() // m_vars is initialised when getVars() is first called  

Further explanation can be found here

Thanks for the link. I was already trying to further wrap the static in a class, but I hadn’t thought of wrapping it in a function.

My situation is a bit more complicated because I am actually trying to create an std::shared_ptr of type var. I didn’t bring this up in the last post because it didn’t seem relevant then, but it seems relevant now.

The behavior I’m trying to replicate is this:

class SomeClass
{
    static const std::shared_ptr<var> m_vars;
}

(Let’s forget about the std::vector for now, although it will be there in my final solution).

Using the Construct on First Use Idiom, I need to use a function to declare the static. Should that function return static const std::make_shared like this?

class SomeClass
{
       const std::shared_ptr<var>& getVars()
       {      //                   vvv
               static const std::make_shared<var> m_vars (0.4f);
               return m_vars;
       }
}

Or should the function return a std::shared_ptr instead of std::make_shared? I didn’t know about static declarations inside functions until today, so I’m not yet able to picture how they will interact with the reference count inside shared_ptr.

Note: a static std::shared_ptr might seem like a dubious concoction to begin with, but I believe that it makes sense in the context I’m working in. I can elaborate if necessary.

Maybe explain what you’re trying to achieve?

There is no point having a static shared_ptr. In general, I’d avoid using static with any complex objects.

1 Like

IIRC the method that returns the static instance can itself be static, saving you the need to create a temporary object just to call it, so your code becomes:

class SomeClass
{
public:   // you can spare 'public' only if you declare this a struct instead of a class
       static const std::vector<var>& getVars()
       {
               static const std::vector<var> m_vars;
               return m_vars;
       }
}

and you access it via SomeClass::getVars()

1 Like

@Anima
Yeah, I know it sounds a bit crazy. I suppose it’s good for me to take the time to spell this out in full, so that I can get feedback on whether the whole thing makes sense or not.

Short version: I’m actually not trying to create a static std::shared_ptr, but rather trying to create a static object which has a shared_ptr as an argument. This runs into the same problems as described above, which is why I simplified it a bit in the previous posts.

Long version: I have a functor class CopyPropertiesFromVector that I’m using to copy properties from an std::vector to a ValueTree. Now CopyPropertiesFromVector has a std::shared_ptr to another CopyPropertiesFromVector object, which it can fall back to if this one doesn’t work. The class is working really well, and I want to keep the shared_ptr functionality, as it gives me a lot of flexibility.

But there are some copy profiles that I’m using quite commonly, so I thought I’d make them static. So what I want to create is something like the following pseudo-code:

static const CopyPropertiesFromVector commonCopyProfile (vectorOfVars, std::make_shared<CopyPropertiesFromVector> (secondVector, nullptr)); 

That’s when I ran into these problems, since the secondVector argument is calling vars which haven’t been initialized yet.

I hope this makes sense, and would welcome feedback. I’m still hoping that there’s some trick that will allow me to make it static.

I think shared_ptr is not what you want in this case. A shared_ptr is a solution to share the ownership of an object between multiple objects or local references, that have an interest.

static variables have an undetermined lifetime, so the lifetime of the object in your shared_ptr gets an undefined lifetime too, which is probably not what you want.

It sounds to me like you are after either a singleton or a SharedResourcePointer. Could that be a cleaner alternative?

The second time you’re telling me this :slight_smile:

Could you explain the difference between SharedResourcePointer and std::shared_ptr?

Ah yes, I remember :slight_smile:

A SharedResourcePointer is basically a singleton, but it is created and destroyed on the fly. Every time you need an object of X just create a SharedResourcePointer and done. If there is already one, you get it. If not it is created. A good use case is the AudioFormatManager. You need it from time to time, but actually you don’t care if it’s there or not. You just don’t want to create it too often. So when your DAW restores a session, it needs it often, so there is a high chance, there is already one present, and it will use it. But later, when all your sounds are in memory, nobody needs it, so in a SharedResourcePointer, it will be removed, vs. a singleton would linger around until the program is closed (or it is manually removed).

A shared_ptr is completely separate from these two, and has a totally different use case:
Two objects have an interest in X. X shall be present, as long as one of the interested ones are alive.
To allow that, the object X has a reference counter, it knows, how many people are looking at it. And if the last one switched off the telly, the object knows, it can go home, nobody is watching any longer.

The shared_ptr is the same like juce’s ReferenceCountedObject with an implementation detail different, intrusive vs. non intrusive reference count, which probably doesn’t matter for you at the moment.