QSettings、QApplication相关的 BUG 一例

本文探讨了一个关于 Qt 中 GUI 相关 QVariant 类型注册与反注册的问题,详细解析了在程序退出时出现崩溃的原因,并提供了代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题重现

看看下面这个简单的程序,能猜出会发生什么问题么?

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

该程序退出时会崩溃。今天几乎用了一天的时间来定位这个bug,不过根本原因总算找到了。并可用前面的代码进行重现。

原因

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。此处a的析构函数被调用,QSettings 被激活。但是,在析构a之前,QAcpplication析构函数中已经将qMetaTypeGuiHelper置位0。于是,悲剧了

疑问?

既然 QApplication 负责注册与反注册,可是为什么,为什么? 如果我们前面的代码中不使用 QApplication 而使用 QCoreApplication 的话,却不会出错,这又是为何??

看看前面调用的两个函数:

源码文件:qguivariant.cpp

int qRegisterGuiVariant()
{
...
    qMetaTypeGuiHelper = qVariantGuiHelper;
    return 1;
}
Q_CONSTRUCTOR_FUNCTION(qRegisterGuiVariant)

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

函数很简单,不简单之处在于,此处多了两个宏:

# 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全局变量,编译器会强制 qRegisterGuiVariant() 在 main 函数之前被执行。
  • 构造另一个全局对象,程序退出时,其析构函数被执行,进而调用 qUnregisterGuiVariant();

这样看来,QApplication 中的动作反而有点多此一举,而且提前调用了一次qUnregisterGuiVariant(),还导致我们前面的问题。(当然,官方这样应该有自己的理由,只是我们尚不太清楚罢了)

小记

程序中使用多个dll动态库,而且用了 有点Qt特色的单例模式,结果导致了bug定位相当困难。

不过呢,收获似乎还不错。


QSettings是Qt框架提供的一个类,用于读写应用程序的持久设置。它支持多种后端存储(如ini文件、系统注册表等),方便地让开发者存储配置信息。 当你在使用QSettings写中文字符,如果出现了大量的%字符,这通常是因为编码方式不一致导致的。QSettings默认采用的是UTF-8编码,如果系统或程序设置的编码不是UTF-8,或者是由于保存和读取编码设置不一致,就可能出现乱码。 为了避免这种情况,你应该确保使用一致的编码方式来保存和读取QSettings中的数据。具体的做法如下: 1. 在写入中文数据,确保你的数据是以UTF-8编码格式存入QSettings的。 2. 在读取数据,同样也要以UTF-8编码格式来读取,这样可以正确地解和显示中文字符。 如果你使用的是C++,在写入和读取可以使用QTextStream来确保正确编码: ```cpp QSettings settings("config.ini", QSettings::IniFormat); // 写入中文数据 settings.beginGroup("MyGroup"); settings.setValue("ChineseName", QString::fromUtf8("中文名字")); settings.endGroup(); // 读取中文数据 settings.beginGroup("MyGroup"); QString chineseName = settings.value("ChineseName").toString(); settings.endGroup(); ``` 在上面的代码中,使用`QString::fromUtf8()`将UTF-8编码的字符串转换为QString对象,这样可以确保写入使用正确的编码。读取QSettings会根据它的默认编码读取数据,所以不需要特别的转换。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值