Question

Language: C++ Toolkit: Qt4

The toolkit I'm using has a static method called int QEvent::registerEventType() to register my own event types. When I subclass this QEvent I need to supply the base class this value. QEvent::QEvent(int type).

Is it ok to use a static variable to call this before application starts? Consider the following:

//This is all in my .cpp file

static int myEventType;  //This will contain my registered type

/*If I create a static instance of this class the constructor 
  gets called before the main() function starts.
*/
class DoRegisterMyEventType {  
public:
  DoRegisterMyEventType() {
    myEventType = QEvent::registerEventType();
  }
};

static DoRegisterMyEventType doRegisterMyEventType;

//Here is the constructor for MyEvent class which inherits QEvent.
MyEvent::MyEvent()
  : QEvent(myEventType)
{
}

How 'evil' is this? I could wrap the whole thing in a namespace to prevent polluting the global namespace.

Was it helpful?

Solution

Static level initialization is a huge compiler-dependent grey area, as others have mentioned. However, function level initialization is not a grey area and can be used to your advantage.

static inline int GetMyEventType()
{
    static int sEventType = QEvent::registerEventType();
    return sEventType;
}

MyEvent::MyEvent()
  : QEvent(GetMyEventType())
{
}

This solution has the property that registerEventType is guaranteed to be called before you need your event type even if you construct MyEvent during static initialization, which is good, but it does open you up to thread-safety issues if it's possible for MyEvent to be constructed on multiple threads.

Here's a thread-safe version, based on boost::call_once:

#include "boost/thread/once.hpp"

static boost::once_flag sHaveRegistered = BOOST_ONCE_INIT; //This is initialized statically, effectively at compile time.    
static int sEventType = -1; //-1 is not a valid event

static void DoRegister()
{
    sEventType = QEvent::registerEventType();
}

static inline int GetMyEventType()
{
    boost::call_once(sHaveRegistered, &DoRegister);
    return sEventType;
}

OTHER TIPS

Since C++'s initialization across TUs is a big grey area with much implementation leeway, I prefer to scrap it completely and be explicit about what gets done when. (This rejection of initialization order due to lack of guarantees is similar to how singleton classes reject global objects.) Specifically, this means any global state (global variables, static data members, and function-local statics) that cannot be initialized with constant-expressions must be initialized in exactly one TU, and that TU is the one that implements main.

In the manual case, this means inserting and updating code in the translation unit that contains main and in main itself. The most common example of such code is calling srand(time(0)) to seed the std::rand PRNG.

You can refactor that manual code management using the preprocessor:

// the implementation file for main, could be named main.cpp

#include "whatever_declares_the_real_main.hpp"

#include "global_objects.inc"

int main(int argc, char* argv[]) try {
#include "main_init.inc"

  return the_real_main(argc, argv);

  // main.cpp has well-defined responsibility:
  // initialize global state before passing control to another function, and
  // handle return-code or exceptions

  // you can modify this, depending on your preference and desired API
  // for example:
  return the_real_main(std::vector<std::string>(argv+1, argv+argc));
  return the_real_main(parse_args(argv+1, argv+argc));
  // just make sure to keep main.cpp's responsibility well-defined and
  // relatively simple
}
// example handling; depending on your specifics, you might do something
// different, or know how to provide more information:
catch (std::exception& e) {
  std::cerr << "abnormal termination: " << e.what() << '\n';
  return 1;
}
catch (...) {
  std::cerr << "abnormal termination.\n";
  return 1;
}

These .inc files are neither headers nor implementation files. The exact file extension doesn't matter as long as you don't use something which is commonly used for headers or implementation files, such as .h, .hpp, .cc, .cpp, and so forth. You can generate global_objects.inc and main_init.inc based off file-naming conventions, using include guards so that dependencies may be included (just as include guards work for headers).

For example, both of these files correspond with myevent.hpp and would be placed alongside that header:

// file "myevent.global_inc"
#ifndef INCLUDE_GUARD_37E6F5857F8F47918A7C83F29A9DA868
#define INCLUDE_GUARD_37E6F5857F8F47918A7C83F29A9DA868

#include <QEvent.hpp> // or whatever headers you need

#include "myevent.hpp" // declares the variable defined just below
// (remember you use 'extern' to declare objects without defining them)

int your_namespace::myEventType = QEvent::registerEventType();

#endif

// file "myevent.main_inc"
#ifndef INCLUDE_GUARD_4F1B93D0F4D3402B802CBA433241AA81
#define INCLUDE_GUARD_4F1B93D0F4D3402B802CBA433241AA81

// nothing needed in this case, from what you've shown so far

// this is where you place expressions that would otherwise require a dummy
// global variable to make sure they are executed, but this also allows use
// of temporary variables while includes handle dependency order:
#include "something_else.main_inc" // fake example dependency, which must
{                                  // be executed first
  int temp;
  some_func(&temp);
  other_func(temp); // not easy to transform this into a global's init
  // expression, yet defining it this way is natural, because it's exactly
  // how you would do it inside a function
}

#endif

Note that if you only require static data initialization with constant-expressions, then that is preferred over all other techniques. The primary restriction for that initialization is not being able to make a function call (but it's actually more complex), so it doesn't apply in your case; this is the only kind of global variable initialization that C can do, if you want to find out more.

I use the "static register object" pattern quite a bit, but you must be aware of one big problem - you must ensure that the thing you are registering with, which itself is likely to be static, is created before the thing you are registering. As C++ does not guarantee the order of static construction between translation units, this can be problematic. One solution is to use the so called Meyer Singleton:

class Registry {
  public:
    static Registry & Instance() {
        static Registry r;
        return r;
    }

    ... 

 private:
    Registry() {    
      ...
    }
};

As all references to the Registry must go through the Instance() method, you are guaranteed the required construction order.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top