-2

While working on a library depending on another third party library, I ran into the problem that the third party library is absolutely trash requires a manual call to a global setup and cleanup function.

int main()
{
    setup();
    //do stuff
    cleanup();
}

Now, this is a total sh*t show not much of a problem in application code since it is just syntactically horrifying, but is actually a pain in a library.

The library is supposed to abstract these weird implementation details away, and requiring the user to call a setup function is like slapping my own face.

I tried to make them disappear

//namespace scope
struct AutoMagic
{
    AutoMagic() {setup();}
    ~AutoMagic() {cleanup();}
};
AutoMagic automagic;

And then I realized this won't work across translation units as seen here and there

Thus the question in the title.

Passer By
  • 19,325
  • 6
  • 49
  • 96
  • I suppose this is a matter of an opinion, and in my opinion manual setup/teardown is a right way to go. It gives more control to the user of this library. What if I wanted/needed to do something before starting up that library? What if there are some configuration options which can be passed to it prior to/on startup? Now, if you want to do it automagically, just go ahead and do what you did. It would work, by which I mean it would call `setup` some time before `main` starts. Why would you care about order of global initializers? – Andrey Turkin May 02 '17 at 16:35
  • @AndreyTurkin Users might want global variables. In which case things would break. Opinions differ, but I don't want my library to come with caveats: "you shall not have global variables from this library", or "if calls to this library is performed before setup, the program is ill-formed". This kind of behaviour propagates through the layers of libraries too, meaning the end user might be so far from the actual setup that the code look like a tiny speck of dust – Passer By May 02 '17 at 16:39
  • Users can use global variables and everything will work fine unless some global constructor uses that library. In which case you have a dependency and poor 3rd party library author didn't had a chance in hell to do a right thing anyway (just how was he/she supposed to handle that to your satisfaction?) The only option I know for this case is to check and initialize the library in each public API function using something like `std::call_once`. – Andrey Turkin May 02 '17 at 16:45
  • @AndreyTurkin My library depends on the third party library, I *need* a call `setup()` for my library to work. If a user decides he wants a global variable of a type from my library, he can't. Because he wouldn't know if the setup is done yet. I have considered something along the lines of `std::call_once` in every public API, but I don't think this makes the library very maintainable and error free. – Passer By May 02 '17 at 16:52
  • So... does your library has some shared state? To make things a bit more func - with non-trivial constructors; say, some global cache protected by a global mutex? If it doesn't, imagine it does (because it does - initialization of 3rd party library IS basically a shared state). Now, how would you go about making sure your library users can use your library API in their global variables? And now imagine there are library users who want to make sure some of their code runs before your library does *anything*, and then they want some of their code run after your library cleaned up *everything*. – Andrey Turkin May 02 '17 at 17:01
  • @AndreyTurkin I think you just proved my point: these setup functions shouldn't exist in the first place, they are basically impossible to manage and introduces global state. I am attempting to stop the bleeding at the layer of my library so that higher end users won't have to face this dilemma again. Emphasis on attempting – Passer By May 02 '17 at 17:07
  • You called some library a trash (btw which library is it?). Let's assume that the library has to have a shared state. Put yourself in author's shoes: you've got a state, some nice internal setup/teardown functions; setup must be called before shared state can be used by anything in the library. How would you go about calling setup? Let's say an user needs a way to cleanup that state (e.g. on dll unload if library is in dll). And of course there can be several users of the library in single app, each users calls cleanup but you must not destroy state until every one did. How do you handle that? – Andrey Turkin May 02 '17 at 17:58
  • @AndreyTurkin I think we should agree to disagree, this will lead nowhere – Passer By May 02 '17 at 18:30
  • Just saying, if you are dissing someone's solution to a problem, you'd better be able to provide a better one. So far I've seen 3 approaches - a manual call to setup/cleanup functions ("trash"; quite widely used by library authors), lazy initialization (requires at least one line at the start of each API function, not good enough for you; also presents an issue with cleanup on dll unload), and global objects (doesn't work well with other global objects; also not an option for C libraries). You might not like these options, but you still need to find some way to deal with shared state. – Andrey Turkin May 02 '17 at 22:24

3 Answers3

0

You could try something like this:

std::shared_ptr<AutoMagic> init(){
    static std::shared_ptr<AutoMagic> ptr(new AutoMagic{});
    return ptr;
}

and in every translation unit do:

auto libMagic = init();

I didn't test it, but whichever translation unit calls init() first should create the object and whichever shared_ptr is unloaded last should call the destructor.

Anedar
  • 4,235
  • 1
  • 23
  • 41
  • Syntax might be messed up a bit, but i hope you get the idea and are able to figure the rest out. I cant test it currently. – Anedar May 02 '17 at 16:26
  • This didn't solve the problem, I don't want to require this kind of setup/teardown code *anywhere and anyhow*. It might be inevitable, but one can always hope – Passer By May 02 '17 at 16:26
  • Well, you dont actually need it anywhere and anyhow - you would only need it in translation units where the initialization of global objects relies on a previously called `init` - but there you have to call it somehow anyways. For the user loading your library, all your global objects will be initialized before he can call functions/instanitiate objects, so for this a single shared_ptr in your library should be sufficient. – Anedar May 02 '17 at 21:23
  • The problem is how to prevent users from even realizing there was setup happening, not how to let users safely setup. A library is meant to do that, implementation details should be 100% opaque if at all possible. – Passer By May 03 '17 at 10:31
0

In the end, the only viable thing left is unportable compiler specific code

AutoMagic automagic __attribute__((init_priority(420));

Unportable as in, basically all mainstream compilers support this except msvc.

This attribute forces the variable to be initialized before all variables without this attribute and those with priority number greater than this variable.

This goes to illustrate that there is indeed a demand for initialization priority guarantees and that it is obviously still not being given by the standard.

Passer By
  • 19,325
  • 6
  • 49
  • 96
-1

Sadly, you can't really get out of this. You had a nice idea with the global instance, but indeed that doesn't work too well with dependencies. It has the further problem of potentially being optimised away by the linker unless you "use" it or are careful with build options; I encountered this issue with instances of an entity registration class inside my shared library.

Two solid options:

  1. Add similar pieces of cr*p setup/teardown functions to your own library, that defer to setup() and cleanup()
  2. Change libraries

I would apologise profusely, but really this is the third-party author's fault. So you should k*ll th*m get them to apologise.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055