QSettings、QLPplication関連のBUGの一例


問題の再現


次の簡単なプログラムを見て、何が起こるか推測できますか?
#include <QtCore/QSettings>
#include <QtGui/QApplication>
#include <QtGui/QColor>

class A:public QObject
{
public:
    A(QObject *parent):QObject(parent){}
    ~A()
    {
        QSettings settings("test.ini", QSettings::IniFormat);
        settings.setValue("color", QColor(Qt::red));
    }
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    A * a = new A(&app);
    return 0;
}

プログラムが終了するとクラッシュします.今日はほぼ1日かけてこのバグを特定しましたが、根本的な原因はやっと見つかりました.前のコードで再現できます.

の原因となる


QAPplication構造とプロファイリング時には,逆登録されたGUIに関連するVariantタイプ(Font,Colorなど)が登録される.
QApplication::QApplication(int &argc, char **argv)
{
...
    // trigger registering of QVariant's GUI types
    qRegisterGuiVariant();
...
}

QApplication::~QApplication()
{
...
    // trigger unregistering of QVariant's GUI types
    qUnregisterGuiVariant();
}

QSetttingsがQFont、QColorなどをプロファイルに書き込む際に必要な情報は、QSettingsだけでなく、QMetaType::saveを呼び出す必要があるものはすべてこの問題があり、QVariantのデータをQdataStreamストリームに書き込む.
bool QMetaType::save(QDataStream &stream, int type, const void *data)
{
...
    switch(type) {
...
    case QMetaType::Long:
        stream << qlonglong(*static_cast<const long *>(data));
        break;
    case QMetaType::Int:
        stream << *static_cast<const int *>(data);
        break;
...
    case QMetaType::QFont:
    case QMetaType::QPixmap:
    case QMetaType::QBrush:
    case QMetaType::QColor:
    case QMetaType::QPalette:
...
    case QMetaType::QQuaternion:
        if (!qMetaTypeGuiHelper)
            return false;
        qMetaTypeGuiHelper[type - FirstGuiType].saveOp(stream, data);
        break;
}

問題の原因:aのparentはQAPplicationオブジェクトappであるため、appは最後に自動的にdeleteを削除するように構成されている.ここでaの構造関数が呼び出され,QSettingsがアクティブになる.ただし、QACpplication解析関数では、aを解析する前にqMetaType GuiHelperが0にセットされている.そして、悲劇は

質問?


QAPplicationが登録と反登録を担当している以上、なぜですか.もし私たちの前のコードでQLPplicationを使わずにQCoreApplicationを使えば、間違いはありません.これはなぜですか?
前に呼び出された2つの関数を見てみましょう.
ソースファイル:qguivariant.cpp
int qRegisterGuiVariant()
{
...
    qMetaTypeGuiHelper = qVariantGuiHelper;
    return 1;
}
Q_CONSTRUCTOR_FUNCTION(qRegisterGuiVariant)

int qUnregisterGuiVariant()
{
...
    qMetaTypeGuiHelper = 0;
    return 1;
}
Q_DESTRUCTOR_FUNCTION(qUnregisterGuiVariant)

関数は簡単ですが、ここには2つのマクロが追加されています.
# define Q_CONSTRUCTOR_FUNCTION0(AFUNC) \
   static const int AFUNC ## __init_variable__ = AFUNC();
# define Q_CONSTRUCTOR_FUNCTION(AFUNC) Q_CONSTRUCTOR_FUNCTION0(AFUNC)

# define Q_DESTRUCTOR_FUNCTION0(AFUNC) \
    class AFUNC ## __dest_class__ { \
    public: \
       inline AFUNC ## __dest_class__() { } \
       inline ~ AFUNC ## __dest_class__() { AFUNC(); } \
    } AFUNC ## __dest_instance__;
# define Q_DESTRUCTOR_FUNCTION(AFUNC) Q_DESTRUCTOR_FUNCTION0(AFUNC)

少し乱れているようで、私たちは一目見ました.
static const int qRegisterGuiVariant__init_variable__ = qRegisterGuiVariant();

class qUnregisterGuiVariant__dest_class__ {
public: \
    inline qUnregisterGuiVariant__dest_class__() { }
    inline ~ qUnregisterGuiVariant__dest_class__() { qUnregisterGuiVariant(); }
} qUnregisterGuiVariant__dest_instance__;

すべてが明らかで、
  • staticグローバル変数を構築し、コンパイラはmain関数の前にqRegisterGuiVariant()を強制します.
  • は別のグローバルオブジェクトを構築し、プログラムが終了すると、その構造関数が実行され、qUnregisterGuiVariant()が呼び出される.

  • このようにQLPplicationでの動作は、かえって多くのことをしており、qUnregisterGuiVariant()を事前に呼び出して、私たちの前の問題を引き起こしています.(もちろん、公式には自分の理由があるはずですが、私たちはまだよく知らないだけです)

    メモ


    プログラムには複数のdllダイナミックライブラリが使用され,少しQtの特色のある単例モードが使用され,結果としてバグの位置決めがかなり困難になった.
    でもね、収穫は悪くないようです.