有两种可能的方式:
- 将所有设置加载到某个结构中
- 按需加载值
哪种方法更好?
这取决于您使用设置文件的方式。您想允许您的应用程序的用户动态更改文件中的设置(例如.ini 文件)吗?还是必须由 GUI 设置设置?
如果您使用某些 GUI 来更改设置,我建议您在应用程序开始时从静态类加载主要设置。
void SettingsManager::loadSettings()
{
// .ini format example
QSettings settings(FileName, QSettings::IniFormat);
IntegerSetting = settings.value("SettingName", default).toInt();
BooleanSetting = settings.value("SettingName", default).toBool();
// ...
}
然后,由于QSettings优化,按需保存更改的值没有问题。
/**
* key is your setting name
* variant is your value (could be string, integer, boolean, etc.)
*/
void SettingsManager::writeSetting(const QString &key, const QVariant &variant)
{
QSettings settings(FileName, QSettings::IniFormat);
settings.setValue(key, variant);
}
如果您担心,可以将每个逻辑设置组放在接口后面。然后,构建一个使用 QSettings 按需检索设置的具体类。
如果您发现这是性能瓶颈,请构建一个缓存设置的具体类。(我从来不需要这样做。QSettings 一直都足够快。)
我不会完全回答你的问题,因为你问的是一个错误的问题;)你问的是阅读设置。QSettings()
使用构造和调用读取QSettings::value()
几乎从来都不是问题,我所有的测量都表明它非常非常快,非常接近 0 毫秒。关于你的问题:我会直接读取数据,即没有中间结构。拥有另一层只是复杂性,不值得为可能的同步付出努力。现在是真正的问题。
然而,一个很大的问题是写入设置。如果您使用本机存储进行设置,这在 Windows 上也相当快,这是 Windows 注册表(Windows 上的默认设置)。注册表由操作系统优化,缓存在 RAM 中,因此写入也不会导致延迟。然而在 Linux 上,这似乎是一个非常不同的故事。以下内容与 Linux 相关(在我的例子中是 Ubuntu 和 Kubuntu)。
我没有详细研究 Qt 的源代码,但我所有的测量结果表明,在普通磁盘上进行任何更改后将设置写入磁盘至少需要大约 50 毫秒,SSD 可能会更快一些。在我看来,当QSettings
对象数据已更改并且对象被销毁或应用程序事件循环准备好做一些工作(即不忙于重绘或处理其他事件)时,调用保存操作。然后将设置刷新到磁盘。
因此,我会警告不要QSettings().setValue(key, value);
在您关心速度的任何地方调用它。因为这将导致对象销毁时立即进行保存操作并导致延迟。
如果您只调用一次设置的保存操作,例如在关闭应用程序时,您可以轻松支付 50 毫秒,节省时间不是问题。但这通常不是您想要的。您希望动态保存应用程序状态。换句话说,当您更改应用程序中的某些内容,然后在不关闭第一个实例的情况下打开该应用程序的另一个实例,并且您希望新实例已经具有新设置。在这种情况下,您必须在完成任何更改后立即保存所有内容,而不仅仅是在应用程序关闭时。然后节省时间成为一个大问题。
我是怎么做的。我创建了一个Settings
具有静态方法并提供类似 API 作为QSettings
对象的单例类。在这个单例对象中,我QSettings
只创建一次对象(就在我实例化之后QApplication
),并且只在应用程序结束时销毁它一次。在我的代码中,我调用Settings::value(key)
或Settings::setValue(key, value)
在需要时调用。优点是只有在事件循环准备好时才保存设置。当然,这仍然需要 50 毫秒,但可以肯定的是,它只会被调用一次,并且会保存同时缓存的所有更改。QSettings().setValue(key, value)
与每次调用 save相比,这是一个很大的改进,如果您进行多次此类调用,可能会阻塞您的 UI。
您当然可以通过多种方式实现单例。我使用的是这个:
设置.h:
#pragma once
#include <QSettings>
/// Singleton! Create only one instance!
class Settings
{
public:
Settings();
~Settings();
static bool contains(const QString &key);
static QVariant value(const QString &key, const QVariant &defaultValue = QVariant());
static void setValue(const QString &key, const QVariant &value);
private:
static Settings *s_instance;
QSettings m_settings;
};
设置.cpp:
#include "settings.h"
Settings *Settings::s_instance = nullptr;
Settings::Settings()
{
Q_ASSERT(s_instance == nullptr);
s_instance = this;
}
Settings::~Settings()
{
Q_ASSERT(s_instance != nullptr);
s_instance = nullptr;
}
bool Settings::contains(const QString &key)
{
return s_instance->m_settings.contains(key);
}
QVariant Settings::value(const QString &key, const QVariant &defaultValue)
{
return s_instance->m_settings.value(key, defaultValue);
}
void Settings::setValue(const QString &key, const QVariant &value)
{
s_instance->m_settings.setValue(key, value);
}
主.cpp:
...
Application application; // must be created before settings
Settings settings; // create settings singleton
application.exec() // runs event loop - settings is stored whenever event loop is ready
// settings destroyed here
// application destroyed here
...
而在其余的代码中,只需调用Settings::setValue(key, value);
.
请注意,即使这个解决方案对于一些时间非常关键的用例来说也不够好。例如,考虑通过拖动鼠标来调整拆分器或窗口的大小。您希望它流畅并同时保存设置,对吗?为了实现平滑,您不能在拖动过程中保存它,而只能在拖动完成后保存。因此,不要将设置保存在鼠标移动事件中。您只想在拖动完成后更改设置。要实现这一点,您将不得不使用事件过滤器做一些巧妙的技巧,也许继承和自定义库存的 Qt 小部件或其他东西,只是根据您的需要。但这是不同的故事和不同的问题。
在 的文档中QSettings
,它说它已经被优化得非常好。
在内部,它保留了 QStrings 到 QVariants 的映射。所有访问器方法都非常有用且易于使用。
当我使用QSettings
时,我将其设置为类似于他们使用readSettings()
和writeSettings()
函数的示例。在页面的一半左右看到这个例子。
在我调用readSettings()
QSettings 对象的那一刻,它会按需加载值,并将所有设置保存在某个结构中。
因此,在我的主要功能中,我确保设置了我的应用程序名称和组织名称,并且我还使用QSettings::setFormat
了 ,然后每当我想访问 QSettings 时,我都会使用默认参数创建一个 QSettings 实例并访问设置。
QSettings s;
int val = s.value("Some_Group/some_setting", default_value).toInt();
// ...
s.setValue("Some_Group/some_setting", val);