Qt插件的析构函数不被调用(QTBUG17458)

起源:因为发现插件析构函数老是不被调用,最终注意到该bug。

简单陈述一些东西,不做加工

QTBUG-17458

该BUG内容:

  • Manual 中说:程序结束时,插件会自动被unload,因此不需要手动调用QPluginLoader::unload()

bool QPluginLoader::unload ()
Unloads the plugin and returns true if the plugin could be unloaded; otherwise returns false.
This happens automatically on application termination, so you shouldn't normally need to call this function.
  • 事实上,程序结束时,插件不会被unload,从而插件中root对象的析构函数也不会被调用。

官方尝试

  • 2010年6月4日 397295f1a91c782f905374213b85ef1108c357e3,添加了程序退出时unload插件的代码

struct LibraryData {
    LibraryData() : settings(0) { }
    ~LibraryData() {
         foreach(QLibraryPrivate *lib, loadedLibs) {
             lib->unload();
        delete settings;
    }

    QSettings *settings;
    LibraryMap libraryMap;
};
Q_GLOBAL_STATIC(LibraryData, libraryData)

它是借助于LibraryData的析构函数实现的。

  • 2010年9月29日,6a1f951ff321982413b462b79293b273bbcc00de,对于一些老的插件,程序退出时的unload代码,会导致程序崩溃。故而删除了这部分代码。

     LibraryData() : settings(0) { }
     ~LibraryData() {
         delete settings;
-        foreach(QLibraryPrivate *lib, loadedLibs) {
-            lib->unload();
-        }
     }

插件构造、析构

看看插件的 new 和 delete 分别藏身何处

构造

所有的插件我们都会使用下面这个宏:

Q_EXPORT_PLUGIN2(PluginName, ClassName)
  • PluginName 可以任意取,它只对静态插件有意义。

  • ClassName 是要导出的插件类的类名

  • 该宏展开后有newClassName(详见Qt插件学习一):

extern "C" Q_DECL_EXPORT QObject* qt_plugin_instance()
{
     static QPointer<QObject> _instance;
     if (!_instance)
     _instance = new ClassName;
     return _instance;
}
  • QPluginLoader会解析去这个函数

bool QLibraryPrivate::loadPlugin()
{
    instance = (QtPluginInstanceFunction)resolve("qt_plugin_instance");

而后调用

QObject *QPluginLoader::instance()
{
...
        return d->instance();

析构

调用unload时,先调用 delete 删除插件对象

bool QLibraryPrivate::unload()
{
    if (!libraryUnloadCount.deref()) { // only unload if ALL QLibrary instance wanted to
        if (instance)
            delete instance();
        if  (unload_sys()) {
            instance = 0;
            pHnd = 0;
        }
    }

题外

乱七八糟,罗列与此:

QTextCodec

QTextCodec相关的new、delete问题一则一文中,QTextCodec 插件所用的方法或许也可以借鉴一下。

不过看看QTBUG-4341,问题还是好复杂啊

static storage duration

C++中的Storage Druation分4种:

  • static storage duration
  • thread storage duration
  • automatic storage duration
  • dynamic storage duration

由于thread那个目前尚没人用,所以static storage duration应该是最复杂的一个。也在Qt中也是大量使用的一个。

不过Google的C++ Style Guide却并不建议对类对象使用这个东西:

Static or global variables of class type are forbidden: they cause hard-to-find bugs due to indeterminate order of construction and destruction.

Objects with static storage duration, including global variables, static variables, static class member variables, and function static variables, must be Plain Old Data (POD): only ints, chars, floats, or pointers, or arrays/structs of POD.

The order in which class constructors and initializers for static variables are called is only partially specified in C++ and can even change from build to build, which can cause bugs that are difficult to find. Therefore in addition to banning globals of class type, we do not allow static POD variables to be initialized with the result of a function, unless that function (such as getenv(), or getpid()) does not itself depend on any other globals.

Likewise, the order in which destructors are called is defined to be the reverse of the order in which the constructors were called. Since constructor order is indeterminate, so is destructor order. For example, at program-end time a static variable might have been destroyed, but code still running -- perhaps in another thread -- tries to access it and fails. Or the destructor for a static 'string' variable might be run prior to the destructor for another variable that contains a reference to that string.

As a result we only allow static variables to contain POD data. This rule completely disallows vector (use C arrays instead), or string (use const char []).

If you need a static or global variable of a class type, consider initializing a pointer (which will never be freed), from either your main() function or from pthread_once(). Note that this must be a raw pointer, not a "smart" pointer, since the smart pointer's destructor will have the order-of-destructor issue that we are trying to avoid.

顺序

对于static storage duration的对象:

  • 在全局范围中定义的对象,它的构造函数在文件中的所有函数执行之前调用。但如果一个程序中有多个文件,而不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。当main函数执行完毕或调用exit函数时,调用析构函数。
  • 在函数中定义静态对象,则在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。

全局变量与动态库

C++ 标准对涉及到动态库的东西似乎没有任何规定。

  • http://msdn.microsoft.com/en-us/library/988ye33t.aspx提到:

    Each time a new process attempts to use the DLL, the operating system creates a separate copy of the DLL's data: this is called process attach. The run-time library code for the DLL calls the constructors for all of the global objects, if any, and then calls the DllMain function with process attach selected. The opposite situation is process detach: the run-time library code calls DllMain with process detach selected and then calls a list of termination functions, including atexit functions, destructors for the global objects, and destructors for the static objects. Note that the order of events in process attach is the reverse of that in process detach.

参考


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值