Using static variables for program initialization/de-initialization?

Started by
7 comments, last by Antheus 15 years, 3 months ago
As I do more and more programming, I find myself using some external libraries (like cURL). Some of these libraries require me to call a global init function and then a global cleanup function, and the libraries require that the init/cleanup functions get called once, and only once. The global init function must be called before any library feature are used, and the global cleanup function must be called when the library is no longer going to be used. I wouldn't mind, except for the fact that I'm using these external libraries in my own library, which I will be using in various projects. So I was pondering about a way to auto-call the global init/cleanup functions (so the user wouldn't have to) and I thought of using a static variable whose constructor/destructor made the calls. Something like this: SomeFile.cpp
class MyInitializer
{
    // Make the constructor private so no one can create an instance of this class
    MyInitializer()
    {
        some_global_init_function_for_some_library();
    }
    
    ~MyInitializer()
    {
        some_global_cleanup_function_for_some_library();
    }
    
    // make it volatile so it doesn't get optimized away
    static volatile MyInitializer variable;
};

Would something like this work ok? Is it legal, safe, and does it actually do what I want it too?
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]
Advertisement
It's not entirely safe; the library you're using might not have had it's static variables initialised at that point, and calling a function in the library could lead to undefined behaviour.

However, you could instance that class at the start of main / WinMain - it'd go out of scope at the correct time, and cleanup the library.
Quote:Original post by Evil Steve
It's not entirely safe; the library you're using might not have had it's static variables initialised at that point, and calling a function in the library could lead to undefined behaviour.

However, you could instance that class at the start of main / WinMain - it'd go out of scope at the correct time, and cleanup the library.

Ah yes, I had completely forgotten about the other library's static variables. Good catch! Ok, well it looks like I'm either going to have to give my library some init/cleanup functions the user must call or some object with a singleton-like behavior (by singleton-like, I mean only one can be created, which means after it's been destroyed another can't be created). Any thoughts on the two or suggestions?
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]
I'm using gcc which has the (highly non-portable, but otherwise great) attributes "constructor" and "destructor".
This calls the attributed functions just before entering main() and just before exiting. That's cool for calling the Winsock init functions too, without cluttering up main() with #ifdefs, and works like a charm.
Quote:Original post by samoth
I'm using gcc which has the (highly non-portable, but otherwise great) attributes "constructor" and "destructor".
This calls the attributed functions just before entering main() and just before exiting. That's cool for calling the Winsock init functions too, without cluttering up main() with #ifdefs, and works like a charm.


Oooo that's nifty. Unfortunately I'm currently working in MSVC and the code needs to be highly portable. But I am going to have to remember that for future uses.
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]
FYI: MSVC also has #pragma init_seg, although it doesn't seem to give a way to have code execute after all static initialisation.
Quote:
Some of these libraries require me to call a global init function and then a global cleanup function, and the libraries require that the init/cleanup functions get called once, and only once. The global init function must be called before any library feature are used, and the global cleanup function must be called when the library is no longer going to be used.


Do these calls have to happen globally? Why not use a class that calls init() in the constructor and deinit() in the destructor (or call init manually and let the class only care for deinit()). Create an instance as the first thing in main(). Don't use any global objects, at least not if they need to call the library functions in the constructor/destructor.

Would this be out of the question?
Fortunately, in this case, the solution is idiomatic.
CURL *curl_easy_init( );
translates into C++ as:
struct curl : boost::noncopyable {  curl() {    : handle(curl_easy_init())  {     if (handle == NULL) throw;  }  ~curl() {    curl_easy_cleanup(handle);  }  CURLcode set_options(CURLoption option, parameter);  CURLcode perform();private:  CURL * handle;};


And that's about it.

Most C libraries have OO interfaces anyway, but above approach can be applied to most semi-reasonably engineered C libraries.

Note that cURL doesn't really require explicit call to global_init(), and that it's safe to call it multiple times. The CURL * handle however must be cleaned up, which is accomplished with above wrapper.

Also in case of cURL, you have curl_easy_duphandle(), so the wrapper doesn't need to be non-copyable.
Quote:Original post by Antheus
***snip***


Hmm, I like that object wrapper idea. If I wanted to make it copyable, I'd just overload the = operator and the copy constructor and use the curl_duphandle function, right? But I have one more question (actually it's the same as my first question):

Quote:http://curl.haxx.se/libcurl/c/curl_easy_init.html
If you did not already call curl_global_init(3), curl_easy_init(3) does it automatically. This may be lethal in multi-threaded cases, since curl_global_init(3) is not thread-safe, and it may result in resource problems because there is no corresponding cleanup.
That bold part kinda worries me. I want my application to be as safe and stable as possible in both single and multi-threaded environments. This is why I want to call curl_global_init. Oh, hey, here's an idea:

class CurlHandle{    public:        CurlHandle()        {            if (initialized == false)            {                curl_global_init();                initialized = true;            }            handle = curl_easy_init();        }        CurlHandle(const CurlHandle& h)        {            handle = curl_easy_duphandle(h.handle);        }        ~CurlHandle()        {            curl_easy_cleanup(handle);        }        CurlHandle& operator = (const CurlHandle& h)        {            curl_easy_cleanup(handle);            handle = curl_easy_duphandle(h.handle);            return *this;        }        CURLcode setOptions(CURLoption option, ...)        {            // I'm not sure how to pass the parameters from the "..." to curl_easy_setopt        }        CURLcode preform()        {            return curl_easy_perform(handle);        }    private:        CURL* handle;        static bool initialized = false;        class Deinitializer        {            Deinitializer() {};            ~Deinitializer()            {                curl_global_cleanup();            }            static volatile Deinitializer cleaner;        };};


With that code you get the effects of the convenient wrapper you kindly made, as well as greater multi-threading safety (right?). Plus this class waits for the static objects to be constructed (but I'm assuming that last part might not be true if I made a static instance of CurlHandle... not that I'm planning too, plus this will be a private class for my project internals and not used by others). Is this safe?

[edit]

On second thought, I don't think this is safe because cURL's own static variables may be getting destructed when my Deinitializer calls the cleanup function. I suppose I could keep a counter that keeps track of how many CURL handles there are, and if there are zero handles it calls the cleanup function. Is this even worth it? Or should I just cross my fingers and use curl_easy_init() and hope there aren't any issues if the program is used in multi-threaded projects?

[Edited by - MikeTacular on December 18, 2008 8:50:26 PM]
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]
Quote:Original post by MikeTacular

Is this safe?


No.
if (initialized == false)            {                curl_global_init();  <---- danger zone                initialized = true;            }

If multiple threads start at same time, they will all pass the first == check before being able to set initialized to true.

To solve this reliably while retaining the convenience, I'd find a solidly implemented multi-threaded singleton which would hold instance of a mutex (or critical section) upon which your constructor would lock.

Something like:
CurlHandle(){  boost::scoped_lock lock(get_curl_mutex());  handle = curl_easy_init(); // calls global init}

The problem is in get_curl_mutex(). It would be trivial if it could be implemented as:
boost::mutex & get_curl_mutex() {  static boost::mutex curl_mutex;  return curl_mutex;}

Unfortunately, the above isn't thread-safe either. The innocent curl_mutex is a non-atomic statement. During initialization, several threads would call get_curl_mutex() and, seeing it doesn't exist, would proceed to initialize it, in turn calling constructor while one thread might be already claiming a lock on it.

It's text-book case of why I dislike singletons and static context in multi-threaded environments, since they end up being a huge can of worms. To achieve the above reliably, I'd look into some tested and reviewed thread-safe singleton implementation which does take care of all those details.


Of course, all of the above can be avoided if you manually initialize the library, but it seems this is what you're trying to avoid.

Quote:I don't think this is safe because cURL's own static variables may be getting destructed when my Deinitializer calls


If I understand the documentation, then global_init() doesn't have a clean-up function, so you cannot clean up global state accidentally.

This topic is closed to new replies.

Advertisement