【发布时间】:2012-12-31 04:47:18
【问题描述】:
有两种可能的方法:
- 将所有设置加载到某个结构中
- 按需加载值
哪种方法更好?
【问题讨论】:
-
这是一个文本编辑器。窗口最大化、最近文件等设置。
有两种可能的方法:
哪种方法更好?
【问题讨论】:
这取决于您使用设置文件的方式。您想让您的应用程序的用户动态更改文件(例如.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 ms。关于您的问题:我会直接读取数据,即没有中间结构。拥有另一层只是复杂性,不值得为可能的同步付出努力。现在是真正的问题。
然而,写入设置是一个很大的问题。如果您使用本机存储进行设置,这在 Windows 上也相当快,这是 Windows 注册表(Windows 上的默认设置)。注册表由操作系统优化,缓存在 RAM 中,因此写入也不会导致延迟。然而在 Linux 上,这似乎是一个非常不同的故事。以下内容与 Linux 相关(在我的例子中是 Ubuntu 和 Kubuntu)。
我没有详细研究 Qt 的源代码,但我所有的测量结果表明,在普通磁盘上,任何更改后将设置写入磁盘至少需要大约 50 毫秒,SSD 可能会更快一些。在我看来,当 QSettings 对象数据已更改并且对象被销毁或应用程序事件循环准备好执行某些工作(即不忙于重绘或处理其他事件)时,将调用保存操作。然后将设置刷新到磁盘。
因此,我会警告不要在您关心速度的任何地方调用此QSettings().setValue(key, value);。因为这会在对象销毁时导致立即保存操作,并会导致延迟。
如果您只调用一次设置的保存操作,例如在关闭应用程序时,您可以轻松支付50毫秒,保存时间不是问题。但这通常不是您想要的。您希望动态保存应用程序状态。换句话说,当您更改应用程序中的某些内容,然后在不关闭第一个实例的情况下打开该应用程序的另一个实例,并且您希望新实例已经具有新设置。在这种情况下,您必须在完成任何更改后立即保存所有内容,而不仅仅是在应用程序关闭时。然后节省时间成为一个大问题。
我是怎么做的。我创建了一个单例类Settings,它具有静态方法并提供与QSettings 对象类似的API。在这个单例对象中,我只创建了一次QSettings 对象(就在我实例化QApplication 之后),并且只在应用程序结束时销毁它一次。在我的代码中,我会在需要时调用Settings::value(key) 或Settings::setValue(key, value)。优点是只有在事件循环准备好时才保存设置。当然,这仍然需要 50 毫秒,但可以肯定的是,它只会被调用一次,并且会保存同时缓存的所有更改。与 QSettings().setValue(key, value) 相比,这是一个很大的改进,QSettings().setValue(key, value) 每次都会调用 save 并且如果您进行多次此类调用可能会阻塞您的 UI。
您当然可以通过多种方式实现单例。我用的是这个:
settings.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;
};
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);
}
main.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() 函数将其设置为类似于他们的示例。在页面的一半左右看到这个example。
当我调用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);
【讨论】: