Заметки по языку с и с++

Готовый класс для работы с конфигурацией приложения (обертка над QSettings)

Введение

QSettings предоставляет кросс-платформенный доступ к настройкам приложения (реестр Windows, .ini-файлы, plist на macOS), но прямое использование QSettings с обращением по строковым ключам по всему коду — источник опечаток и отсутствия единого источника правды о доступных настройках. В статье построим типобезопасную обёртку над QSettings с явно объявленными свойствами и значениями по умолчанию.

Концепция

Обёртка инкапсулирует QSettings и предоставляет строго типизированные методы доступа для каждой настройки приложения, со значениями по умолчанию, заданными в одном месте — это устраняет дублирование строковых ключей и значений по умолчанию по всему коду, и даёт компилятору возможность поймать опечатку в имени метода (но не в строковом ключе самого QSettings, который остаётся скрытым внутри реализации).

Пример кода

#pragma once
#include <QSettings>
#include <QString>
#include <QColor>

class AppConfig
{
public:
    static AppConfig &instance()
    {
        static AppConfig instance;
        return instance;
    }

    // --- Настройки окна ---
    QSize windowSize() const
    {
        return m_settings.value("window/size", QSize(1024, 768)).toSize();
    }
    void setWindowSize(const QSize &size)
    {
        m_settings.setValue("window/size", size);
    }

    bool isMaximized() const
    {
        return m_settings.value("window/maximized", false).toBool();
    }
    void setMaximized(bool maximized)
    {
        m_settings.setValue("window/maximized", maximized);
    }

    // --- Настройки темы ---
    QString themeName() const
    {
        return m_settings.value("appearance/theme", "light").toString();
    }
    void setThemeName(const QString &theme)
    {
        m_settings.setValue("appearance/theme", theme);
    }

    // --- Сетевые настройки ---
    int requestTimeoutMs() const
    {
        return m_settings.value("network/timeoutMs", 5000).toInt();
    }
    void setRequestTimeoutMs(int timeoutMs)
    {
        m_settings.setValue("network/timeoutMs", timeoutMs);
    }

    void sync()
    {
        m_settings.sync();
    }

private:
    AppConfig() : m_settings("MyCompany", "MyApp") {}

    QSettings m_settings;
};
// Использование
#include "appconfig.h"
#include <QDebug>

void applyStartupSettings()
{
    auto &config = AppConfig::instance();

    qDebug() << "Размер окна:" << config.windowSize();
    qDebug() << "Тема:" << config.themeName();

    config.setThemeName("dark");
    config.sync(); // принудительная немедленная запись на диск
}

Пояснения к коду

Каждая настройка приложения представлена парой методов-геттер/сеттер с осмысленным именем (windowSize()/setWindowSize()), внутри которых строковый ключ QSettings и значение по умолчанию указаны ровно один раз — изменение значения по умолчанию или структуры ключей требует правки в одном месте, а не поиска по всему проекту. Иерархические ключи вида "window/size" группируют связанные настройки в QSettings, что отражается, например, в виде разделов в .ini-файле на платформах, использующих этот формат.

Метод sync() явно сбрасывает изменения на диск — QSettings обычно делает это автоматически при удалении объекта или периодически, но явный вызов полезен в местах, где критично гарантировать сохранение настроек немедленно (например, перед потенциально аварийным завершением).

Подводные камни

  • Несогласованность типа значения по умолчанию и типа, возвращаемого .toType(). Если QSettings хранит значение, записанное в формате, не полностью совместимом с ожидаемым типом (например, после ручного редактирования .ini-файла пользователем), .toSize()/.toInt() может вернуть не то, что ожидается, без явной ошибки — для критичных настроек стоит дополнительно валидировать прочитанные значения.
  • Singleton-обёртка усложняет тестирование кода, зависящего от конфигурации, аналогично проблеме, описанной для общего паттерна Singleton — для тестируемого кода предпочтительнее явно передавать ссылку на конфигурацию (или интерфейс над ней) через конструктор, а не обращаться к AppConfig::instance() напрямую из бизнес-логики.
  • Отсутствие миграции настроек между версиями приложения. Если структура ключей меняется между версиями (переименование, изменение формата хранимого значения), старые установленные версии приложения могут оставить устаревшие или несовместимые значения в хранилище — нужна явная логика миграции/очистки устаревших ключей при обновлении.
  • QSettings не предназначен для хранения чувствительных данных (паролей, токенов) в открытом виде — на Windows реестр и .ini-файлы не зашифрованы по умолчанию, и для секретов нужно использовать отдельное защищённое хранилище (системный keychain/credential manager), а не общий механизм настроек приложения.