强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Qt 与 GTK 图形框架教程 / 16 - 最佳实践 / Best Practices

最佳实践 / Best Practices

掌握 Qt/GTK 应用的架构模式、性能优化、内存管理和国际化最佳实践。 Master architecture patterns, performance optimization, memory management, and i18n.


16.1 架构模式 / Architecture Patterns

MVC 模式 / Model-View-Controller

Qt 本身采用的是模型-视图架构(没有显式控制器),控制器逻辑通常集成在视图或代理中。 Qt natively uses Model-View (no explicit Controller); controller logic lives in views or delegates.

┌───────────┐     ┌───────────────┐     ┌───────────┐
│   Model   │────▶│    View       │◀────│ Controller│
│  (数据)   │     │  (显示)       │     │  (逻辑)   │
│           │     │               │     │           │
│QSqlTable  │     │QTableView     │     │QWidget    │
│Model      │     │QTreeView      │     │Delegate   │
└─────┬─────┘     └───────┬───────┘     └─────┬─────┘
      │                   │                   │
      └───────────────────┴───────────────────┘
              signals / slots / events

MVVM 模式 / Model-View-ViewModel

MVVM 更适合 QML 项目,ViewModel 通过 Q_PROPERTYQ_INVOKABLE 暴露给 QML。

┌───────────┐     ┌───────────────┐     ┌───────────┐
│   Model   │────▶│   ViewModel   │────▶│   View    │
│  (数据)   │     │  (状态+命令)  │     │  (QML)    │
│           │     │               │     │           │
│QNetwork   │     │QObject        │     │QML/       │
│Manager    │     │Q_PROPERTY     │     │QtQuick    │
│QSqlQuery  │     │Q_INVOKABLE   │     │           │
└───────────┘     └───────────────┘     └───────────┘

MVVM 示例代码(QML + C++) / MVVM Example

// userviewmodel.h
#ifndef USERVIEWMODEL_H
#define USERVIEWMODEL_H

#include <QObject>
#include <QQmlEngine>
#include <QStringListModel>

class UserViewModel : public QObject {
    Q_OBJECT
    QML_ELEMENT

    Q_PROPERTY(QString userName READ userName
               WRITE setUserName NOTIFY userNameChanged)
    Q_PROPERTY(QString email READ email
               WRITE setEmail NOTIFY emailChanged)
    Q_PROPERTY(bool isValid READ isValid NOTIFY validationChanged)
    Q_PROPERTY(QStringList userList READ userList NOTIFY userListChanged)
    Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged)

public:
    explicit UserViewModel(QObject *parent = nullptr) : QObject(parent) {
        loadUsers();
    }

    QString userName() const { return m_userName; }
    QString email() const { return m_email; }
    bool isValid() const { return m_isValid; }
    QStringList userList() const { return m_userList; }
    QString errorMessage() const { return m_errorMessage; }

    void setUserName(const QString &name) {
        if (m_userName != name) {
            m_userName = name;
            emit userNameChanged();
            validate();
        }
    }

    void setEmail(const QString &email) {
        if (m_email != email) {
            m_email = email;
            emit emailChanged();
            validate();
        }
    }

    Q_INVOKABLE void addUser() {
        if (!m_isValid) return;

        // 业务逻辑
        m_userList.append(m_userName + " <" + m_email + ">");
        emit userListChanged();

        // 重置
        m_userName.clear();
        m_email.clear();
        emit userNameChanged();
        emit emailChanged();
        validate();
    }

    Q_INVOKABLE void removeUser(int index) {
        if (index >= 0 && index < m_userList.size()) {
            m_userList.removeAt(index);
            emit userListChanged();
        }
    }

signals:
    void userNameChanged();
    void emailChanged();
    void validationChanged();
    void userListChanged();
    void errorMessageChanged();

private:
    void validate() {
        bool valid = true;
        QString error;

        if (m_userName.trimmed().isEmpty()) {
            valid = false;
            error = "请输入姓名";
        } else if (!m_email.contains('@')) {
            valid = false;
            error = "邮箱格式无效";
        }

        if (m_isValid != valid) {
            m_isValid = valid;
            emit validationChanged();
        }
        if (m_errorMessage != error) {
            m_errorMessage = error;
            emit errorMessageChanged();
        }
    }

    void loadUsers() {
        m_userList = {"张三 <zhangsan@example.com>",
                      "李四 <lisi@example.com>"};
    }

    QString m_userName;
    QString m_email;
    bool m_isValid = false;
    QStringList m_userList;
    QString m_errorMessage;
};

#endif
// UserPage.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import MyApp

Page {
    UserViewModel { id: viewModel }

    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 16
        spacing: 12

        Text {
            text: "添加用户"
            font.pixelSize: 20
            font.bold: true
        }

        TextField {
            id: nameField
            placeholderText: "姓名"
            text: viewModel.userName
            onTextChanged: viewModel.userName = text
            Layout.fillWidth: true
        }

        TextField {
            id: emailField
            placeholderText: "邮箱"
            text: viewModel.email
            onTextChanged: viewModel.email = text
            Layout.fillWidth: true
        }

        Text {
            text: viewModel.errorMessage
            color: "#e74c3c"
            visible: !viewModel.isValid
        }

        Button {
            text: "添加"
            enabled: viewModel.isValid
            onClicked: {
                viewModel.addUser();
                nameField.text = "";
                emailField.text = "";
            }
            Layout.fillWidth: true
        }

        ListView {
            Layout.fillWidth: true
            Layout.fillHeight: true
            model: viewModel.userList
            delegate: RowLayout {
                width: ListView.view.width
                Label {
                    text: modelData
                    Layout.fillWidth: true
                }
                Button {
                    text: "删除"
                    flat: true
                    onClicked: viewModel.removeUser(index)
                }
            }
        }
    }
}

16.2 性能优化 / Performance Optimization

Qt 性能优化清单 / Qt Performance Checklist

优化领域 / Area技巧 / Technique影响 / Impact
信号槽使用 Qt::DirectConnection 避免队列开销
绘制重写 paintEvent() 时使用裁剪 (QRegion)
布局避免频繁 invalidate(),使用 QLayout::activate()
模型使用 beginInsertRows() / endInsertRows() 批量插入
图片使用 QPixmap 缓存,避免在 paintEvent 中加载
QSS避免通配符选择器 *,使用 #id
QML使用 Loader 延迟加载,避免深层 Repeater
数据库使用事务、索引、预编译查询
线程将耗时操作移至 QThread / QtConcurrent

延迟加载示例 / Deferred Loading

// QML 延迟加载
StackView {
    id: stackView
}

// 使用 Loader 延迟加载
Loader {
    id: heavyComponent
    active: false
    sourceComponent: Component {
        HeavyVisualization { }
    }
}

Button {
    text: "加载可视化"
    onClicked: heavyComponent.active = true
}
// Qt Widgets 延迟加载
class MainWindow : public QMainWindow {
    void showSettings() {
        if (!m_settingsDialog) {
            m_settingsDialog = new SettingsDialog(this); // 首次使用时创建
        }
        m_settingsDialog->show();
    }
    SettingsDialog *m_settingsDialog = nullptr; // 延迟初始化
};

数据库优化 / Database Optimization

// 预编译查询(复用)
class UserRepository {
public:
    UserRepository() {
        // 预编译,后续调用只绑定参数
        m_insertQuery.prepare(
            "INSERT INTO users (name, email, age) VALUES (?, ?, ?)");
    }

    int create(const QString &name, const QString &email, int age) {
        m_insertQuery.bindValue(0, name);
        m_insertQuery.bindValue(1, email);
        m_insertQuery.bindValue(2, age);
        m_insertQuery.exec();
        return m_insertQuery.lastInsertId().toInt();
    }

private:
    QSqlQuery m_insertQuery;
};

// 批量插入使用事务
void batchInsert(const QStringList &names) {
    QSqlDatabase::database().transaction();
    QSqlQuery query;
    query.prepare("INSERT INTO users (name) VALUES (?)");
    for (const auto &name : names) {
        query.bindValue(0, name);
        query.exec();
    }
    QSqlDatabase::database().commit();
}

16.3 内存管理 / Memory Management

Qt 内存管理规则 / Qt Memory Rules

规则 / Rule说明 / Description
对象树父对象析构时自动删除所有子对象 / Parent deletes children
parent 参数new QWidget(parent) — 传递 parent 确保自动释放
deleteLater()跨线程删除使用 deleteLater(),不要直接 delete
智能指针业务对象使用 QSharedPointerstd::unique_ptr
避免裸指针优先使用栈对象或智能指针
// ✅ 好的做法
class MainWindow : public QMainWindow {
    // parent 自动管理子控件生命周期
    auto *button = new QPushButton("Click", this);

    // deleteLater 安全删除
    QObject *obj = createWorker();
    connect(worker, &Worker::finished, obj, &QObject::deleteLater);

    // 非 QObject 使用智能指针
    auto config = std::make_unique<AppConfig>();
    auto data = QSharedPointer<DataModel>::create();
};

// ❌ 坏的做法
void badExample() {
    QWidget *w = new QWidget();     // 无 parent,泄漏!
    deleteLater(w);                  // 不要这样
    delete w;                        // 如果 w 有 pending 事件,崩溃!
}

GTK 内存管理 / GTK Memory Management

/* GObject 引用计数 */
MyObject *obj = my_object_new();
g_object_ref(obj);    // 增加引用
g_object_unref(obj);  // 减少引用
g_object_unref(obj);  // 最后一次释放

/* GtkWindow 等由 GTK 管理 */
GtkWidget *window = gtk_window_new();  // 浮动引用
gtk_window_present(GTK_WINDOW(window)); // sink 浮动引用,GTK 接管

/* GBoxed 类型 */
gchar *str = g_strdup("Hello");
g_free(str);

GVariant *variant = g_variant_new_string("test");
g_variant_unref(variant);

16.4 国际化 (i18n) / Internationalization

Qt 国际化流程 / Qt i18n Workflow

源码 (tr())
    │
    ▼
lupdate ──→ .ts 文件 (XML)
    │
    ▼
翻译工具 (Qt Linguist) ──→ 翻译后的 .ts 文件
    │
    ▼
lrelease ──→ .qm 文件 (二进制)
    │
    ▼
QTranslator::load() ──→ 运行时加载
// 源码中标记可翻译字符串
class MainWindow : public QMainWindow {
    void setupUI() {
        setWindowTitle(tr("我的应用 / My Application"));

        auto *fileMenu = menuBar()->addMenu(tr("文件(&F)"));
        fileMenu->addAction(tr("新建(&N)"));
        fileMenu->addAction(tr("打开(&O)"));

        auto *label = new QLabel(tr("欢迎, %1!").arg(m_userName));

        // 复数形式
        int count = 5;
        label->setText(tr("%n 个文件已选中", nullptr, count));
    }
};

// main.cpp 加载翻译
int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QTranslator translator;
    QString locale = QLocale::system().name();  // "zh_CN"
    if (translator.load("myapp_" + locale, ":/translations")) {
        app.installTranslator(&translator);
    }

    MainWindow w;
    w.show();
    return app.exec();
}
# CMakeLists.txt - 自动生成 .ts 文件
qt_add_translations(myapp
    TS_FILES myapp_zh_CN.ts myapp_en_US.ts
    SOURCES ${SOURCES}
)

Python 国际化 / Python i18n

"""Python 国际化 - 使用 gettext"""

import gettext
import locale

# 设置语言
lang = gettext.translation(
    'myapp',
    localedir='locales',
    languages=['zh_CN'],
    fallback=True
)
_ = lang.gettext

# 使用
print(_("Hello, World!"))
print(_("File saved successfully"))
print(_("Error: {}").format("connection failed"))

GTK C 国际化 / GTK C i18n

/* GTK 国际化使用 gettext */
#include <glib/gi18n.h>

/* 初始化 */
bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
textdomain(GETTEXT_PACKAGE);

/* 使用 */
gtk_button_set_label(button, _("_Save"));
gtk_label_set_text(label, _("Welcome, %s!"), username);
# meson.build - i18n 支持
i18n = import('i18n')

# 编译翻译文件
i18n.gettext(meson.project_name(),
    preset: 'glib',
    locales: ['zh_CN', 'en_US']
)

16.5 错误处理 / Error Handling

Qt 错误处理模式 / Qt Error Patterns

// 1. 信号-槽错误通知
class NetworkService : public QObject {
    Q_OBJECT
signals:
    void requestFinished(const QJsonDocument &result);
    void requestFailed(const QString &error, int code);
};

// 2. Result 模式
template<typename T>
class Result {
public:
    static Result<T> success(T value) { return Result(value, "", true); }
    static Result<T> failure(const QString &error) { return Result(T(), error, false); }

    bool isSuccess() const { return m_success; }
    T value() const { return m_value; }
    QString error() const { return m_error; }

private:
    Result(T value, QString error, bool success)
        : m_value(std::move(value)), m_error(std::move(error)), m_success(success) {}
    T m_value;
    QString m_error;
    bool m_success;
};

// 使用
Result<int> parseInt(const QString &str) {
    bool ok;
    int value = str.toInt(&ok);
    if (ok) return Result<int>::success(value);
    return Result<int>::failure("Invalid number: " + str);
}

GTK 错误处理 / GTK Error Handling

/* GError 模式 */
GError *error = NULL;
gboolean success = my_operation(&error);
if (!success) {
    g_printerr("Error: %s (code: %d)\n", error->message, error->code);
    g_error_free(error);
}

/* GtkAlertDialog 显示错误 */
static void show_error_dialog(GtkWindow *parent, const char *message) {
    GtkAlertDialog *dialog = gtk_alert_dialog_new("错误 / Error");
    gtk_alert_dialog_set_detail(dialog, message);
    const char *buttons[] = {"确定", NULL};
    gtk_alert_dialog_set_buttons(dialog, buttons);
    gtk_alert_dialog_choose(dialog, parent, NULL, NULL, NULL);
}

16.6 代码组织 / Code Organization

myapp/
├── CMakeLists.txt              # 顶层 CMake
├── src/
│   ├── CMakeLists.txt
│   ├── main.cpp                # 入口
│   ├── core/                   # 核心业务逻辑
│   │   ├── application.h/cpp   # QApplication 子类
│   │   ├── config.h/cpp        # 配置管理
│   │   └── constants.h         # 常量定义
│   ├── models/                 # 数据模型
│   │   ├── user.h/cpp
│   │   └── usermodel.h/cpp     # QAbstractTableModel
│   ├── views/                  # 视图/窗口
│   │   ├── mainwindow.h/cpp/ui
│   │   ├── settingsdialog.h/cpp
│   │   └── userwidget.h/cpp
│   ├── viewmodels/             # ViewModel (QML 项目)
│   │   └── userviewmodel.h/cpp
│   ├── repositories/           # 数据访问层
│   │   ├── repository.h        # 基类
│   │   └── userrepository.h/cpp
│   ├── services/               # 业务服务
│   │   ├── authservice.h/cpp
│   │   └── apiservice.h/cpp
│   └── utils/                  # 工具类
│       ├── validator.h
│       └── formatter.h
├── qml/                        # QML 文件
│   ├── Main.qml
│   ├── pages/
│   └── components/
├── resources/                  # 资源文件
│   ├── resources.qrc
│   ├── icons/
│   ├── images/
│   └── translations/
│       ├── myapp_zh_CN.ts
│       └── myapp_en_US.ts
├── tests/                      # 测试
│   ├── CMakeLists.txt
│   ├── test_usermodel.cpp
│   └── test_validator.cpp
└── docs/                       # 文档
    └── CHANGELOG.md

16.7 编码规范 / Coding Conventions

Qt C++ 命名规范 / Qt C++ Naming

元素 / Element规范 / Convention示例 / Example
类名PascalCaseMainWindow, UserModel
函数名camelCasesetValue(), isValid()
成员变量m_ 前缀 + camelCasem_name, m_isValid
局部变量camelCaseuserName, count
常量UPPER_SNAKE_CASEMAX_RETRY_COUNT
枚举PascalCase + PascalCase 成员enum State { Active, Inactive }
信号camelCasevalueChanged(), clicked()
槽函数camelCase, on 前缀可选onClicked(), updateView()

GTK C 命名规范 / GTK C Naming

元素 / Element规范 / Convention示例 / Example
类型名PascalCaseMyWidget, MyObject
函数名小写 + 下划线my_widget_get_name()
大写 + 下划线MY_TYPE_WIDGET
枚举PascalCaseMyWidgetState
信号名小写 + 连字符"count-changed"
CSS 类小写 + 连字符.my-button

16.8 常见反模式 / Anti-Patterns to Avoid

反模式 / Anti-Pattern正确做法 / Correct
new 后忘记 delete传递 parent 或使用智能指针
信号槽中做耗时操作使用 QThread / QtConcurrent
硬编码字符串使用 tr() 标记翻译
QSS 使用 * 通配符使用 #id 或具体类名
QSqlQuery 拼接 SQL使用 ? 参数化查询
忽略 GError始终检查并处理错误
paintEvent 中分配内存缓存 QPixmap / QPicture
全局变量管理状态使用单例或依赖注入

注意事项 / Important Notes

⚠️ 架构选择 / Architecture Choice

  • Qt Widgets 桌面应用 → MVC(QAbstractItemModel + QTableView
  • QML 移动端应用 → MVVM(QObject + Q_PROPERTY + QML)
  • GTK4 应用 → MVC(GListModel + GtkListView

Choose architecture based on your framework and target platform.

⚠️ 性能分析优先 / Profile First

不要盲目优化。先用工具找到瓶颈,再针对性优化。

  • Qt: QML Profiler, GammaRay, Valgrind
  • GTK: sysprof, GNOME Builder 内置分析器

Don’t optimize blindly. Profile first with appropriate tools.


扩展阅读 / Further Reading

资源 / Resource链接 / Link
Qt 最佳实践https://doc.qt.io/qt-6/bestpractices.html
Qt 性能建议https://doc.qt.io/qt-6/performance.html
Qt 国际化https://doc.qt.io/qt-6/internationalization.html
GNOME HIGhttps://developer.gnome.org/hig/
GTK4 性能https://docs.gtk.org/gtk4/performance.html
GObject 内存管理https://docs.gtk.org/gobject/memory.html
Qt 编码规范https://wiki.qt.io/Qt_Coding_Style

15 - Docker 容器化 | 返回目录 / Back to Index