Qt 优雅实现线程安全单例模式(模板化 + 自动清理)

作者:喵个咪日期:2025/11/20

Qt 优雅实现线程安全单例模式(模板化 + 自动清理)

在 Qt 开发中,单例模式是高频使用的设计模式,用于全局共享一个实例(如配置管理、网络服务、日志系统等)。一个健壮的 Qt 单例需要满足 线程安全、自动清理、通用性强、支持任意构造参数 等核心需求。本文将基于模板封装 + 管理器的设计思路,实现一套可直接复用的单例框架,并详细讲解其设计原理与最佳实践。

一、单例模式的核心诉求

在 Qt 环境中,单例的设计需要解决以下关键问题:

  1. 线程安全: 多线程并发调用时避免创建多个实例;
  2. 自动清理: 程序退出时自动释放资源,避免内存泄漏(尤其配合 Qt 的 QCoreApplication::aboutToQuit 机制);
  3. 通用性: 支持任意类作为单例,无需重复编写单例逻辑;
  4. 灵活构造: 支持带参数的构造函数,且不丢失参数语义;
  5. 安全校验: 避免未初始化就调用实例的错误;
  6. 可手动控制: 支持主动初始化 / 销毁单例。

本文实现的单例框架完全满足以上需求,且兼容 Qt 控制台程序、桌面程序等所有场景。

二、实现架构设计

整体架构分为两层:

  1. Singleton 模板类: 负责单例的实例创建、线程安全保护、初始化 / 销毁逻辑,通过模板实现通用性;
  2. SingletonManager 单例管理器: 负责注册所有单例的清理回调,程序退出时统一执行销毁,避免内存泄漏。

核心设计思路:

  • 模板化封装单例逻辑,避免重复编码;
  • QMutex 保证多线程下的实例创建 / 访问安全;
  • 完美转发(std::forward)支持任意构造参数;
  • 清理回调注册到管理器,利用 Qt 的 aboutToQuit 统一触发;
  • 断言(Q_ASSERT)+ 宏定义简化使用,同时提供安全校验。

三、完整代码详解

3.1 单例管理器:SingletonManager.h

管理器的核心作用是「集中管理所有单例的清理逻辑」,避免每个单例单独处理销毁,确保退出时资源释放的一致性。

1#ifndef SINGLETONMANAGER_H
2#define SINGLETONMANAGER_H
3
4#include <QMutex>
5#include <functional>
6#include <map>
7#include <QObject> // 确保 Q_DISABLE_COPY_MOVE 可用
8
9// 单例管理器:统一注册/注销/执行单例清理回调
10class SingletonManager {
11public:
12    // 管理器自身是单例(懒加载,线程安全)
13    static SingletonManager &instance() {
14        static SingletonManager inst; // C++11 后静态局部变量初始化线程安全
15        return inst;
16    }
17
18    /**
19     * @brief 注册单例清理回调
20     * @param fn 清理函数(通常是删除单例实例的 lambda)
21     * @return 注册 ID(用于后续注销),ID > 0
22     */
23    int registerCleanup(std::function<void()> fn) {
24        QMutexLocker locker(&m_mutex); // 加锁保证线程安全
25        int id = m_nextId++;
26        m_funcs.emplace(id, std::move(fn)); // 转移函数所有权,避免拷贝开销
27        return id;
28    }
29
30    /**
31     * @brief 注销清理回调(支持重复调用,安全可重入)
32     * @param id 注册时返回的 ID
33     */
34    void unregisterCleanup(const int id) {
35        QMutexLocker locker(&m_mutex);
36        m_funcs.erase(id); // 不存在的 ID 无副作用
37    }
38
39    /**
40     * @brief 执行所有注册的清理回调(程序退出时调用)
41     * 特点:拷贝回调列表后再执行,避免回调中操作管理器导致死锁
42     */
43    void cleanupAll() {
44        std::map<int, std::function<void()>> copyFuncs;
45        {
46            // 局部作用域:释放锁后再执行回调,提高并发效率
47            QMutexLocker locker(&m_mutex);
48            copyFuncs = m_funcs; // 拷贝回调列表
49            m_funcs.clear();     // 清空原列表,避免重复执行
50        }
51
52        // 按注册顺序执行回调(map 是有序容器,key 递增)
53        for (auto &[id, func] : copyFuncs) {
54            if (func) {
55                try {
56                    func(); // 执行清理逻辑
57                } catch (...) {
58                    // 捕获所有异常,避免单个单例清理失败影响其他
59                    qWarning() << "[SingletonManager] Cleanup failed for id:" << id;
60                }
61            }
62        }
63    }
64
65private:
66    // 私有构造/析构:禁止外部创建实例
67    SingletonManager() = default;
68    ~SingletonManager() = default;
69
70    // 禁用拷贝/移动:确保管理器全局唯一
71    Q_DISABLE_COPY_MOVE(SingletonManager)
72
73    QMutex m_mutex;                          // 保护回调列表的线程安全
74    std::map<int, std::function<void()>> m_funcs; // 存储清理回调(有序)
75    int m_nextId{1};                         // 回调注册 ID 生成器(从 1 开始,0 为无效 ID)
76};
77
78#endif // SINGLETONMANAGER_H
79

设计亮点:

  • 自身是单例: 静态局部变量初始化(C++11 线程安全),无需额外加锁;
  • 线程安全: 所有对回调列表的操作都通过 QMutex 保护;
  • 安全清理: 拷贝回调列表后释放锁,避免回调中调用 unregisterCleanup 导致死锁;
  • 异常隔离: 单个单例清理失败不影响其他,提高程序稳定性;
  • 有序执行: std::map 保证清理顺序与注册顺序一致,解决单例依赖问题。

3.2 单例模板类:Singleton.h

模板类是单例框架的核心,通过泛型封装通用逻辑,支持任意类作为单例,无需修改目标类代码。

1#ifndef SINGLETON_H
2#define SINGLETON_H
3
4#include <QMutexLocker>
5#include <QCoreApplication>
6#include <QDebug>
7#include <type_traits>
8#include <utility> // 用于 std::forward
9
10#include "SingletonManager.h"
11
12// 静态断言:确保 T 是可构造的(避免抽象类作为单例)
13template<typename T>
14constexpr bool is_singleton_valid_v = std::is_constructible_v<T> && !std::is_abstract_v<T>;
15
16// 单例模板类:支持带参数构造、自动注册清理、线程安全访问
17template<typename T>
18class Singleton {
19    // 编译期校验:若 T 不可构造或为抽象类,直接报错
20    static_assert(is_singleton_valid_v<T>, 
21                  "Singleton<T> requires T to be constructible and non-abstract");
22
23public:
24    /**
25     * @brief 初始化单例(必须在调用 instance() 前执行)
26     * @tparam Args 构造函数参数类型
27     * @param args 构造函数参数(完美转发,支持左值/右值)
28     * 特点:自动注册清理回调到 SingletonManager,支持重复调用(仅首次有效)
29     */
30    template<typename... Args>
31    static void init(Args &&... args) {
32        QMutexLocker lockerInit(&mutex()); // 加锁保证初始化线程安全
33        if (instanceRef() != nullptr) {
34            qWarning() << "[Singleton] " << typeid(T).name() << " has already been initialized";
35            return;
36        }
37
38        // 完美转发参数,创建单例实例(支持任意构造参数)
39        instanceRef() = new T(std::forward<Args>(args)...);
40
41        // 注册清理回调(仅首次初始化时注册)
42        if (regIdRef() == 0) {
43            regIdRef() = SingletonManager::instance().registerCleanup([] {
44                QMutexLocker lockerCleanup(&mutex());
45                delete instanceRef(); // 释放单例实例
46                instanceRef() = nullptr; // 重置指针,避免野指针
47                qDebug() << "[Singleton] " << typeid(T).name() << " cleaned up";
48            });
49        }
50    }
51
52    /**
53     * @brief 获取单例实例指针(线程安全)
54     * @return T* 单例指针(非空,调试模式下为空会触发断言)
55     * 注意:必须先调用 init() 初始化,否则调试模式断言失败,release 模式可能崩溃
56     */
57    static T *instance() {
58        QMutexLocker locker(&mutex());
59        Q_ASSERT_X(instanceRef() != nullptr, 
60                   "Singleton::instance()", 
61                   qPrintable(QString("%1 not initialized! Call Singleton<%1>::init() first.").arg(typeid(T).name())));
62        return instanceRef();
63    }
64
65    /**
66     * @brief 手动销毁单例(主动释放资源)
67     * 特点:销毁后可重新调用 init() 再次初始化,支持动态启停
68     */
69    static void shutdown() {
70        QMutexLocker locker(&mutex());
71        if (instanceRef() != nullptr) {
72            delete instanceRef();
73            instanceRef() = nullptr;
74            qDebug() << "[Singleton] " << typeid(T).name() << " shut down manually";
75        }
76
77        // 注销清理回调(避免重复销毁)
78        if (regIdRef() != 0) {
79            SingletonManager::instance().unregisterCleanup(regIdRef());
80            regIdRef() = 0;
81        }
82    }
83
84    // 禁用默认构造/析构:禁止创建 Singleton 实例(仅通过静态方法访问)
85    Singleton() = delete;
86    ~Singleton() = delete;
87
88    // 禁用拷贝/移动:确保单例唯一性
89    Singleton(const Singleton &) = delete;
90    Singleton &operator=(const Singleton &) = delete;
91    Singleton(Singleton &&) = delete;
92    Singleton &operator=(Singleton &&) = delete;
93
94private:
95    /**
96     * @brief 获取单例实例引用(静态局部变量,懒加载)
97     * 注意:静态局部变量初始化线程安全(C++11 标准)
98     */
99    static T *&instanceRef() {
100        static T *inst = nullptr;
101        return inst;
102    }
103
104    /**
105     * @brief 获取互斥锁引用(静态局部变量,懒加载)
106     * 每个单例类拥有独立的互斥锁,避免不同单例间锁竞争
107     */
108    static QMutex &mutex() {
109        static QMutex m;
110        return m;
111    }
112
113    /**
114     * @brief 获取清理回调注册 ID 引用
115     * 用于跟踪是否已注册清理回调,避免重复注册
116     */
117    static int &regIdRef() {
118        static int id = 0;
119        return id;
120    }
121};
122
123/**
124 * @def GET_SINGLETON(Type)
125 * @brief 获取单例指针(可能为 nullptr,需自行判空)
126 * 适用场景:允许单例未初始化的场景(如可选功能模块)
127 */
128#define GET_SINGLETON(Type) (Singleton<Type>::instance())
129
130/**
131 * @def GET_SINGLETON_REF(Type)
132 * @brief 获取单例引用(调试模式下未初始化会触发断言)
133 * 适用场景:确保单例必须存在的核心模块(如配置管理)
134 */
135#define GET_SINGLETON_REF(Type) ([]() -> Type & { \
136    Type *p = Singleton<Type>::instance();       \
137    Q_ASSERT_X(p != nullptr, "GET_SINGLETON_REF", "Singleton not initialized"); \
138    return *p;                                   \
139}())
140
141/**
142 * @def GET_SINGLETON_OR(Type, alt)
143 * @brief 获取单例指针,若未初始化则返回替代值
144 * 适用场景:需要降级策略的场景(如备用服务)
145 */
146#define GET_SINGLETON_OR(Type, alt) (Singleton<Type>::instance() ? Singleton<Type>::instance() : (alt))
147
148#endif // SINGLETON_H
149

设计亮点:

  • 编译期校验: 通过 static_assert 禁止抽象类、不可构造类作为单例,提前暴露错误;
  • 完美转发: std::forward 保留构造参数的左值 / 右值属性,支持任意构造参数(包括临时对象);
  • 独立锁机制: 每个单例类拥有自己的 QMutex,避免不同单例间的锁竞争,提高并发效率;
  • 灵活控制: 支持 init() 重复调用(幂等性)、shutdown() 手动销毁后重新初始化;
  • 安全宏定义: 提供三种访问宏,适配不同使用场景,调试模式下有明确断言提示。

3.3 目标单例类示例:MyService.h

为了让示例更完整,这里提供一个典型的 Qt 单例类实现(支持信号槽、带参数构造):

1#ifndef MYSERVICE_H
2#define MYSERVICE_H
3
4#include <QObject>
5#include <QString>
6#include <QDebug>
7
8// 示例单例类:网络服务管理(支持信号槽,带参数构造)
9class MyService : public QObject {
10    Q_OBJECT
11public:
12    /**
13     * @brief 带参数构造函数(单例的构造参数通过 Singleton::init() 传递)
14     * @param serverUrl 服务端地址
15     * @param port 服务端口
16     * @param parent 父对象(建议设为 nullptr,避免生命周期冲突)
17     */
18    explicit MyService(const QString &serverUrl, int port, QObject *parent = nullptr) 
19        : QObject(parent), m_serverUrl(serverUrl), m_port(port) {
20        qDebug() << "[MyService] Initialized with URL:" << serverUrl << ", port:" << port;
21    }
22
23    ~MyService() override {
24        qDebug() << "[MyService] Destructor called";
25    }
26
27    // 示例业务方法
28    void doSomething() {
29        qDebug() << "[MyService] Doing something with" << m_serverUrl << ":" << m_port;
30        // 实际业务逻辑:如网络请求、数据处理等
31    }
32
33signals:
34    // 示例信号:如服务状态变化
35    void serviceReady();
36
37private:
38    QString m_serverUrl; // 服务端地址
39    int m_port;          // 服务端口
40};
41
42#endif // MYSERVICE_H
43

注意事项:

  • 单例类若继承 QObject,建议将父对象设为 nullptr,避免 Qt 父子对象生命周期管理与单例冲突;
  • 构造函数需为 publicprotected(若为 protected,需将 Singleton<T> 设为友元);
  • 避免在构造函数中执行耗时操作(如网络连接),可提供 initService() 等方法延迟初始化。

完整使用示例(main 函数)

1#include <QCoreApplication>
2#include <QTimer>
3#include "SingletonManager.h"
4#include "Singleton.h"
5#include "MyService.h"
6
7int main(int argc, char *argv[]) {
8    // 1. 必须先创建 QCoreApplication(QMutex、QObject 等依赖 Qt 环境初始化)
9    QCoreApplication app(argc, argv);
10
11    // 2. 连接 aboutToQuit 信号,程序退出时统一清理单例
12    QObject::connect(&app, &QCoreApplication::aboutToQuit, []() {
13        qDebug() << "\n[Main] Starting singleton cleanup...";
14        SingletonManager::instance().cleanupAll();
15        qDebug() << "[Main] Singleton cleanup finished";
16    });
17
18    // 3. 初始化单例(传递构造参数,自动注册清理回调)
19    Singleton<MyService>::init("http://example.com", 8080);
20
21    // 4. 三种访问单例的方式
22    // 方式1:获取指针(需手动判空,适合可选模块)
23    MyService *svcPtr = GET_SINGLETON(MyService);
24    if (svcPtr) {
25        svcPtr->doSomething();
26    }
27
28    // 方式2:获取引用(调试模式断言非空,适合核心模块)
29    MyService &svcRef = GET_SINGLETON_REF(MyService);
30    svcRef.doSomething();
31
32    // 方式3:获取指针或替代值(适合降级策略)
33    MyService *svcOr = GET_SINGLETON_OR(MyService, nullptr);
34    if (svcOr) {
35        svcOr->doSomething();
36    }
37
38    // 5. 示例:手动销毁单例(可选)
39    QTimer::singleShot(2000, []() {
40        qDebug() << "\n[Main] Shutting down MyService manually...";
41        Singleton<MyService>::shutdown();
42
43        // 销毁后可重新初始化
44        qDebug() << "[Main] Reinitializing MyService...";
45        Singleton<MyService>::init("http://new-example.com", 9090);
46        GET_SINGLETON_REF(MyService).doSomething();
47    });
48
49    // 6. 3秒后退出程序(触发 aboutToQuit 清理)
50    QTimer::singleShot(3000, &app, &QCoreApplication::quit);
51
52    return app.exec();
53}
54

运行输出:

1[MyService] Initialized with URL: "http://example.com" , port: 8080
2[MyService] Doing something with "http://example.com" : 8080
3[MyService] Doing something with "http://example.com" : 8080
4[MyService] Doing something with "http://example.com" : 8080
5
6[Main] Shutting down MyService manually...
7[MyService] Destructor called
8[Singleton] MyService cleaned up
9[Main] Reinitializing MyService...
10[MyService] Initialized with URL: "http://new-example.com" , port: 9090
11[MyService] Doing something with "http://new-example.com" : 9090
12
13[Main] Starting singleton cleanup...
14[MyService] Destructor called
15[Singleton] MyService cleaned up
16[Main] Singleton cleanup finished
17

四、核心注意事项

4.1 初始化顺序

  1. 必须先创建 QCoreApplication QMutexQObject 等 Qt 组件依赖 Qt 环境初始化,因此 QCoreApplication 必须在 Singleton::init() 之前创建;
  2. 单例依赖顺序: 若单例 A 依赖单例 B,需先初始化 B 再初始化 A(清理顺序与注册顺序一致,即先清理 A 再清理 B,避免依赖失效)。

4.2 线程安全

  1. 初始化线程安全: init() 方法通过 QMutex 保护,多线程并发调用仅首次初始化有效;
  2. 访问线程安全: instance() 方法通过 QMutex 保护,避免多线程同时访问未初始化的实例;
  3. 单例内部线程安全: 本文框架仅保证实例创建 / 销毁的线程安全,单例类自身的成员函数需根据业务需求添加锁(如 QMutex)。

4.3 禁止拷贝移动

  • 单例类必须禁用拷贝构造、赋值运算符(通过 Q_DISABLE_COPY_MOVE 或手动删除),否则可能通过拷贝创建多个实例;
  • Singleton<T> 模板已禁用拷贝移动,目标单例类需自行禁用(如示例 MyService 虽未显式禁用,但继承 QObject 后自动禁用)。

4.4 内存泄漏防护

  • 必须连接 QCoreApplication::aboutToQuitSingletonManager::cleanupAll(),否则程序异常退出时可能导致内存泄漏;
  • 若单例类继承 QObject,禁止将其设为其他 QObject 的子对象(否则 Qt 可能自动销毁实例,导致二次释放)。

4.5 调试与 Release 模式差异

  • 调试模式(Debug):instance()GET_SINGLETON_REF 会触发断言,快速定位未初始化的错误;
  • 发布模式(Release):断言失效,instance() 可能返回 nullptr,需自行判空(建议核心模块使用 GET_SINGLETON_REF,非核心模块使用 GET_SINGLETON 并判空)。

五、进阶用法

5.1 懒加载单例(无需手动 init)

默认实现需要手动调用 init(),若需懒加载(首次调用 instance() 时自动初始化),可修改 instance() 方法:

1static T *instance() {
2    QMutexLocker locker(&mutex());
3    if (instanceRef() == nullptr) {
4        // 无参构造(若需带参数,需调整设计,如全局配置)
5        instanceRef() = new T();
6        // 自动注册清理回调
7        if (regIdRef() == 0) {
8            regIdRef() = SingletonManager::instance().registerCleanup([] {
9                QMutexLocker locker(&mutex());
10                delete instanceRef();
11                instanceRef() = nullptr;
12            });
13        }
14    }
15    return instanceRef();
16}
17

注意: 懒加载模式仅支持无参构造,若需带参数,需通过全局配置或其他方式传递参数。

5.2 单例销毁优先级

若需控制单例的销毁顺序(如先销毁依赖方,再销毁被依赖方),可扩展 SingletonManager 支持优先级:

1// 修改 SingletonManager 的注册接口,增加优先级参数
2int registerCleanup(std::function<void()> fn, int priority = 0) {
3    QMutexLocker locker(&m_mutex);
4    int id = m_nextId++;
5    //  pair<priority, id> 作为 key,按优先级降序排序(优先级高的先清理)
6    m_funcs.emplace(std::make_pair(-priority, id), std::move(fn));
7    return id;
8}
9
10// 对应的 map 类型修改为:
11std::map<std::pair<int, int>, std::function<void()>> m_funcs;
12

使用时指定优先级:

1// 高优先级(先清理)
2Singleton<MyService>::initWithPriority(10, "http://example.com", 8080);
3

5.3 线程局部单例(TLS)

若需每个线程拥有独立的单例实例(如线程局部缓存),可修改 instanceRef() 为线程局部变量:

1static T *&instanceRef() {
2    thread_local T *inst = nullptr; // 线程局部变量,每个线程独立
3    return inst;
4}
5
6static QMutex &mutex() {
7    thread_local QMutex m; // 每个线程独立的锁
8    return m;
9}
10

注意:线程局部单例的清理需在线程退出时手动调用 shutdown(),避免线程泄漏。

六、常见问题排查

6.1 断言失败:Singleton not initialized

  • 原因:未调用 Singleton::init() 就调用 instance()GET_SINGLETON_REF
  • 解决:确保 init()instance() 之前调用,且 QCoreApplication 已创建。

6.2 内存泄漏(Valgrind 检测到泄漏)

  • 原因:未连接 QCoreApplication::aboutToQuitSingletonManager::cleanupAll()
  • 解决:在 main 函数中添加连接代码。

6.3 二次释放崩溃

  • 原因:单例类被 Qt 父子对象管理(如设置了父对象),导致 Qt 自动销毁后,SingletonManager 再次销毁;
  • 解决:单例类构造时父对象设为 nullptr,禁止将单例设为其他 QObject 的子对象。

6.4 多线程并发初始化导致崩溃

  • 原因:使用了 C++11 之前的编译器(静态局部变量初始化非线程安全);
  • 解决:升级编译器到支持 C++11 及以上标准,或手动为 instanceRef() 加锁。

七、框架优势总结

本文实现的单例框架相比传统单例(如饿汉式、懒汉式),具有以下优势:

  1. 通用性强: 模板化设计,支持任意可构造类,无需修改目标类代码;
  2. 线程安全: 基于 QMutex 实现初始化 / 访问安全,兼容 Qt 多线程环境;
  3. 自动清理: 通过 SingletonManager 统一管理,避免内存泄漏;
  4. 灵活构造: 支持带参数构造(完美转发),适配复杂单例类;
  5. 安全易用: 提供断言校验、宏定义快捷访问,降低使用成本;
  6. 可扩展: 支持手动销毁、优先级清理、线程局部单例等进阶需求。

该框架可直接用于 Qt 控制台程序、桌面程序(QWidget)、移动程序(Qt Quick)等所有场景,是 Qt 开发中单例模式的优选实现方案。


Qt 优雅实现线程安全单例模式(模板化 + 自动清理)》 是转载文章,点击查看原文


相关推荐


基于UniappX开发电销APP,实现CRM后台控制APP自动拨号
爱心发电丶2025/11/19

原文:nicen.cn/8532.html 在上一篇文章中(juejin.cn/post/757352…),已经实现了电销APP的基础功能:通时通次记录、通话录音上传。 已经能在工作中进行应用了,但是离成熟的电销APP还是差了不少,还得继续开发。 电销APP大都还有一个与之对应的CRM系统,所以另一个常见的需求,就是通过CRM后台直接控制APP拨号。 相关代码和电销APP已经开源:github.com/friend-nice… 开发思路 常规需求用常规的办法:在保证消息收发高效实时的前提下,后端


Swift 6 迁移常见 crash: _dispatch_assert_queue_fail
RickeyBoy2025/11/17

我的 Github:github.com/RickeyBoy/R… 大量 iOS 内容欢迎大家关注~ 最近在将公司项目迁移到 Swift 6 的过程中,解决了好几个相似的 crash。关键字如下 _dispatch_assert_queue_fail "%sBlock was %sexpected to execute on queue [%s (%p)] Task 208: EXC_BREAKPOINT (code=1, subcode=0x103


VSCode debugger 调试指南
清沫2025/11/16

在以前的文章 深入前端调试原理,我们主要从原理的角度来看了如何调试。本篇文章则是从实践角度出发,看看如何在 vscode 中配置和使用各种调试功能。 本文涉及到代码均在仓库 vscode-debugger-dojo,全面的 VSCode Debugger 调试示例项目,涵盖了各种常见场景的调试配置。 VSCode Debugger 原理 在 VSCode 的项目中 .vscode/launch.json 中加入如下的配置即可调试: SCode 并不是 JS 语言的专属编辑器,它可以用于多


Bash 的 base64 命令
hubenchang05152025/11/15

#Bash 的 base64 命令 base64 [OPTION]... [FILE]... 功能 进行 BASE64 编码或解码。 类型 可执行文件(/usr/bin/base64),属于 coreutils。 参数 OPTION 选项: -d, --decode - 解码;不带此选项则为编码 -i, --ignore-garbage - 解码时忽略无效字符 -w, --wrap=COLS - 编码输出时一行的字符数;默认为 76,设为 0 则不换行 --help - 显示帮助 --ve


Vue3实现拖拽排序
用户9714171814272025/11/14

Vue3 + Element Plus + SortableJS 实现表格拖拽排序功能 📋 目录 功能概述 技术栈 实现思路 代码实现 核心要点 常见问题 总结 功能概述 在管理后台系统中,表格数据的排序功能是一个常见的需求。本文介绍如何使用 Vue3、Element Plus 和 SortableJS 实现一个完整的表格拖拽排序功能,支持: ✅ 通过拖拽图标对表格行进行排序 ✅ 实时更新数据顺序 ✅ 支持数据过滤后的排序 ✅ 切换标签页时自动初始化 ✅ 优雅的动画效果 先看实现效果:


Python 的内置函数 id
IMPYLH2025/11/13

Python 内建函数列表 > Python 的内置函数 id Python 的内置函数 id() 是一个非常有用的工具函数,它返回一个对象的"身份标识",这个标识实际上是该对象在内存中的地址(以整数形式表示)。以下是关于 id() 函数的详细说明: 基本用法 x = 42 print(id(x)) # 输出一个整数,代表变量x所引用的对象的内存地址 特性说明 每个对象在内存中都有唯一的id这个id在对象的生命周期内保持不变不同对象可能有相同的id(如果前一个对象已被销毁)


FastAPI × SQLAlchemy 2.0 Async:从“能跑”到“可压测”的完整工程实践
Java私教2025/11/11

一句话总结 用 SQLAlchemy 2.0 AsyncIO 模式,把 FastAPI 的并发优势兑现成 真正的数据库吞吐;再叠上连接池、事务、迁移、测试四件套,直接上线不踩坑。 1. 为什么要“异步 ORM”? 场景同步 SQLAlchemy异步 SQLAlchemy100 个并发上传开 100 线程 → 100 个连接 → DB 被打爆单线程 20 连接即可跑满 CPU请求等待 I/O线程上下文切换 8 ms协程切换 0.3 ms代码风格


删一个却少俩:Antd Tag 多节点同时消失的原因
顺凡2025/11/9

删一个却少俩:Antd Tag 多节点同时消失的原因 需求 一个表单的小需求,能填写多个福利,最多十个,福利名称允许重复,和官方的动态添加和删除示例交互一模一样,只是官方示例不支持 tag 内容重复,使用的 tag 内容作为 key 我复制丢给 AI,下掉去重,限制个数,好!满足需求了,key 值怎么办不能用重复的,拼个索引吧,最后主要代码如下, 反问一下:你觉得这会有什么问题,能达到删一个少俩的效果吗🤔??? 问题 大家应该都知道用 index 作为 key,会有一些问题,对于我这个需


程序员副业 | 2025年10月复盘
嘟嘟MD2025/11/7

本文首发于公众号:嘟爷创业日记 。 我已经坚持日更600天+,欢迎过来追剧~ 大家好,我是嘟嘟MD,一个10年程序员,现在离职创业,有700天了,我每个月都会写一篇总结复盘,让大家可以近距离看看一个离职程序员都在干什么,今天这篇是九月份的总结,大概2000字,略长,有空的可以翻翻,希望对大家有一丢丢的借鉴作用! 一、月度大事 10月结束了,一直拖到现在才有空汇总下10月份的进度,整体来说对外的合作少了,组织内的事情多了。 1:公众号运营+B站视频运营 公众号和B站视频运营还是我的最高优先级


在 Vue3 项目中使用 el-tree
代码工人笔记2025/11/3

在 Vue3 项目中使用 el-tree 文章目录 一、基础用法1. 引入组件 二、常用功能与配置1. 节点选择(复选框 / 单选)2. 展开 / 折叠控制3. 自定义节点内容4. 搜索过滤节点5. 获取选中节点 三、动态加载节点 一、基础用法 1. 引入组件 <template> <el-tree :data="treeData" :props="defaultProps" @node-click="handleNode

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2025 聚合阅读