1

语言:C++ 工具包:Qt4

我正在使用的工具包有一个调用int QEvent::registerEventType()来注册我自己的事件类型的静态方法。当我对它进行子类化时,QEvent我需要为基类提供这个值。QEvent::QEvent(int type).

可以在应用程序启动之前使用静态变量来调用它吗?考虑以下:

//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)
{
}

这有多“邪恶”?我可以将整个东西包装在一个命名空间中,以防止污染全局命名空间。

4

3 回答 3

2

由于跨 TU的 C++ 初始化是一个很大的灰色区域,具有很大的实现余地,因此我更愿意完全放弃它并明确说明何时完成。(由于缺乏保证而拒绝初始化顺序类似于单例类拒绝全局对象的方式。)具体来说,这意味着无法用常量初始化的任何全局状态(全局变量、静态数据成员和函数局部静态)-表达式必须恰好在一个 TU 中初始化,而那个 TU 是实现main的那个。

在手动情况下,这意味着在包含mainmain本身的翻译单元中插入和更新代码。此类代码最常见的示例是调用srand(time(0))播种std::rand PRNG。

您可以使用预处理器重构手动代码管理:

// 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;
}

这些 .inc 文件既不是头文件也不是实现文件。只要您不使用通常用于头文件或实现文件的文件(例如 .h、.hpp、.cc、.cpp 等),确切的文件扩展名并不重要。您可以根据文件命名约定生成global_objects.incmain_init.inc,使用包含保护,以便可以包含依赖项(就像包含保护对标头起作用一样)。

例如,这两个文件都与myevent.hpp相对应,并将放置在该标题旁边:

// 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

请注意,如果您只需要使用常量表达式进行静态数据初始化,那么这比所有其他技术都要好。该初始化的主要限制是无法进行函数调用(但它实际上更复杂),因此它不适用于您的情况;如果您想了解更多信息,这是 C 可以做的唯一一种全局变量初始化。

于 2010-02-25T22:16:34.690 回答
1

我经常使用“静态寄存器对象”模式,但您必须意识到一个大问题 - 您必须确保您正在注册的东西本身可能是静态的,是在您正在注册的东西之前创建的. 由于 C++ 不保证翻译单元之间的静态构造顺序,因此这可能是有问题的。一种解决方案是使用所谓的 Meyer Singleton:

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

    ... 

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

由于对 Registry 的所有引用都必须通过 Instance() 方法,因此您可以保证所需的构造顺序。

于 2010-02-25T20:48:28.293 回答
1

正如其他人所提到的,静态级别初始化是一个巨大的依赖于编译器的灰色区域。但是,函数级初始化不是灰色区域,可以使用对您有利。

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

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

此解决方案具有以下属性:即使您在静态初始化期间构造 MyEvent,也可以保证在需要事件类型之前调用 registerEventType,这很好,但如果可以构造 MyEvent,它确实会给您带来线程安全问题在多个线程上。

这是一个基于 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;
}
于 2010-02-25T23:54:49.010 回答