2

I'm working on a C++ library that I would ideally keep header-only.

A specific part of this library requires a global state.
Let's say it needs a global vector of strings for this example.

I can easily achieve this with a static variable inside a function:

std::vector< std::string > & GetGlobalStrings( void )
{
    static auto g = new std::vector< std::string >();

    return *( g );
}

This works great for executables using the library.

Now for some reason, I also need to package that library inside a macOS framework.

Inside that framework, there's compiled code that will access this global state.
So does the executable linked with that framework.

Obviously this doesn't work, as the executable and the framework will have separate definitions for the static variable, thus making this global state not so global.

Is there any way to accomplish this in a convenient manner?

Macmade
  • 52,708
  • 13
  • 106
  • 123
  • 2
    Do you need to allocate that vector dynamically? Can't you just do `static std::vector g; return g;`? – Galik Nov 23 '18 at 18:12
  • Without a shared library that will support the static variable? Doubtful. You will need to make it non inline, perhaps only the framework has access to the function implementation, and the executable to just the prototype. Do you have a speed issue by making it non header only? – Matthieu Brucher Nov 23 '18 at 18:13
  • I think this can be done with some platform-specific tricks. I don't know anything about MacOS, but on Windows, for example, you could do this with a shared memory block. – Peter Ruderman Nov 23 '18 at 18:20
  • @Galik I'm allocating dynamically so I don't get a warning about requiring an exit-time destructor (-Wexit-time-destructors). Anyway, problem is the same regardless of the allocation type. Even with auto allocation, executable and framework will still have separate definitions. – Macmade Nov 23 '18 at 18:20
  • @MatthieuBrucher No speed issue, header-only is simply more convenient for the library purpose. – Macmade Nov 23 '18 at 18:21
  • Windows or Linux/mac (i.e. hidden symbols or visible symbols by default)? – Matthieu Brucher Nov 23 '18 at 18:23
  • @MatthieuBrucher Library is cross-platform, but the dynamic library requirement is only for macOS. – Macmade Nov 23 '18 at 18:25
  • Could you perhaps utilize C++17 "inline variables"? – Jesper Juhl Nov 23 '18 at 18:48
  • @JesperJuhl C++11 – Macmade Nov 23 '18 at 18:48
  • @Macmade I have doubts about relevance of -Wexit-time-destructors. Good C++ code must not leak resources, automatic destructor call when objects go out of scope is the language mechanism that prevent resource leaking. It would be a big mistake to try to circumvent destruction of objects of static or thread_local storage duration. There is nothing dangerous about having exit-time-destructors unless the desctructor is badly designed; for instance, a destructor that read/write a global object from another compilation unit. – Julien Villemure-Fréchette Nov 23 '18 at 21:42
  • @JulienVillemure-Fréchette I think you meant @Macmade! – Matthieu Brucher Nov 23 '18 at 21:43
  • @JulienVillemure-Fréchette Yes, but this warning flag exists for a reason. Problem is order of global constructors/destructors is undefined. This is why I turn this flag on. Avoids nasty issues when some global object get destroyed before another one that uses the first one. – Macmade Nov 23 '18 at 22:52
  • @Macmade you are right that dynamic initialization and destruction of static storage duration objects from **different translation units** is not guaranteed. This doesn't mean that circumventing destructors call is good design. In fact, a well designed destructor should never depend on a statically allocated object defined in **another translation unit**. A similar rule applies for constructors. Problems resulting from dynamic initialization/program cleanup is just a symptom of bad constructor/destructor design. – Julien Villemure-Fréchette Nov 24 '18 at 00:31

2 Answers2

2

What about:

// GlobalString.h

#include <string>
#include <vector>

#ifdef _MSC_VER
  #ifdef GLOBAL_STRING_SRC
    #define GLOBAL_STRING_DECLSPEC __declspec(dllexport)
  #else
    #define GLOBAL_STRING_DECLSPEC __declspec(dllimport)
  #endif //GLOBAL_STRING_DECLSPEC
#endif // GLOBAL_STRING_SRC

inline EXPORT_SYMBOL std::vector<std::string>& GetGlobalStrings() noexcept
{
  static std::vector<std::string> retval;
  return retval;
}

Then write a .cpp that ODR uses your definition of GetGlobalStrings. On Windows, declaring an function dllexport is an implicit ODR use. Compiling a cpp that includes GlobalString.h and linking it into the dll should work.

// GlobalSring.cpp

#define GLOBAL_STRING_SRC
#include <GlobalString.h>

The inline keyword guarantees that multiple definitions of GetGlobalStrings from different compilation units seen by the linker will be merged into only one if GetGlobalStrings gets ODR used. Be reassured that C++ guarantees that static variables from within inline function are also merged. Note that dllimport on function definition is illegal unless the definition is declared inline. I am not much familar with MacOS dynamic libraries, but it should work similarly with clang with -fvisibility=default flag.

With C++17 one could also probably use an inline variable instead of a fuction:

inline EXPORT_SYMBOL std::vector<std::string> GlobalString;
  • Thanks for the answer. Found a working solution for macOS, not sure I'll run into the same issues on Windows... But I'll keep your answer in mind : ) – Macmade Nov 23 '18 at 22:54
  • 1
    This will still output a stern warning saying that the function is defined when it shouldn't. You will have to mix the two answers to get a good result. – Matthieu Brucher Nov 23 '18 at 23:09
1

You could force the symbols to be in only one file, for instance:

#if defined(I_NEED_A_BAD_HACK) || defined(GLOBAL_STATE_STORE)
# define USE_FULL_FUNCTION
#endif

#ifdef USE_FULL_FUNCTION
std::vector< std::string >& GetGlobalStrings()
{
    static std::vector< std::string > g;

    return g;
}
#else
std::vector< std::string >& GetGlobalStrings();
#endif

Then in one of your framework cpp, define the macro before including the header.

Now, if you need to export the symbol (Windows mainly but also Linux/macOS with visibility hidden), then it gets a little bit trickier, as you need another global flag saying if you are in the framework or not and activating the export/import attribute.

It's definitely not great, but at least you ensure that you have only one instance of the static variable in one file. Also works properly with a static library, of course.

Matthieu Brucher
  • 21,634
  • 7
  • 38
  • 62
  • Thanks. I'm definitely looking in this way, and I actually tried that. Unfortunately, leads to packaging issues, as I would also like to be able to create an executable on other platforms, using only the headers. This would require people to define that macro, which I'd like to avoid... – Macmade Nov 23 '18 at 18:37
  • 1
    Well, I finally made this work, although I had to tweak a bit the build settings in Xcode to allow linking an executable with the headers-only library or with a framework exposing the headers-only library. Anyway, this was the way to go, so marking this as the accepted answer. Thanks : ) – Macmade Nov 23 '18 at 22:58