0

I was trying to think of a way how to deal with C libraries that expect you to globally initialize them and I came up with this:

namespace {

class curl_guard {
public:
    curl_guard()
    {
        puts("curl_guard constructor");
        // TODO: curl_global_init
    }
    ~curl_guard()
    {
        puts("curl_guard destructor");
        // TODO: curl_global_cleanup
    }
};

curl_guard curl{}; // nothing is ever printed to terminal

}

But when I link this into an executable and run it, there's no output, because it is optimized out (verified with objdump), even in debug build. As I understand, this is intended, because this type is never accessed in any way. Is there any way to mark it so that it is not excluded? Preferably without making it accessible to the user of the library. I'd prefer a general solution, but GCC-only also works for my purposes.

I am aware of typical singleton patterns, but I think this is a special case where none of them apply, because I never want to access this even from internals, just simply have a class tucked away which has one simple job of initializing and deinitializing a C library which is caused by the library being linked in and not something arbitrary like "just don't forget to construct this in main" which is as useful as going back to writing C code.

  • 1
    You need to make sure that the standard IO channels are already initialized and setup at that point. – πάντα ῥεῖ Jul 15 '22 at 13:39
  • 3
    Put the curl_guard in the body of main as first local variable (it will have the correct lifetime then). In general globals/singletons are not all that maintainable anyway. And in code it really helps to be explicit instead of implict (your future self will thank you) – Pepijn Kramer Jul 15 '22 at 13:41
  • @PepijnKramer can I ask you which part of my code is unmaintainable right now? And how does introducing user responsibility of `construct B before A` make it more maintainable? If nothing less, I'd rather have my library be less maintainable than every program that uses it. –  Jul 15 '22 at 13:52
  • The RAII setup is fine. The use of globals hinders unit testability (for testing you want to be able to setup/inject objects in fail conditions, e.g. to test your code when curl fails to properly initialize). e.g : [refactoring-out-singletons-globals-to-use-dependency-injection-for-unit-testing](https://stackoverflow.com/questions/21050247/refactoring-out-singletons-globals-to-use-dependency-injection-for-unit-testing) – Pepijn Kramer Jul 15 '22 at 13:58
  • @πάνταῥεῖ Even if that were really the issue, my problem is that it's not even attempting to use IO, because my code is optimized out by the linker... –  Jul 15 '22 at 14:00
  • @PepijnKramer if curl fails then there's no such thing as my code, the system is misconfigured and process should simply abort, not sure what is there to test... –  Jul 15 '22 at 15:34

1 Answers1

0

The real solution was pretty simple - the code can't be separate and needs to be in one of compilation units that are used by the user of the library.

libcurl needs to be initialized globally, and initialization is NOT thread safe, because libraries it depends on also cannot be initialized in thread-safe manner, so it is one of those things where global pre-main initialization is not only convenient, but useful. I in fact am using several other libraries which are like that, and I separate them from libraries that do use threads in the background, by putting those in the main as opposed to pre-main.

And while there are some concerns with doing something like this at all, that's exactly what I need, including library aborting before main even runs if it's installed improperly.

Sure, it's "good practice" to ignore critical errors just to please the users of libraries who insist on libraries aborting being oh so terrible, but I'm sure noone likes to know that in next 50 seconds, they will be dead due to flying vertically downwards due to something that could've been found and fixed early.