信号和槽的链接方式
1.自动连接(Qt::AutoConnection)
描述:这是Qt的默认连接方式。根据信号和槽所属的线程关系自动选择连接方式。
行为:如果信号和槽在同一个线程中,则使用直接连接。
如果信号和槽在不同线程中,则使用队列连接。
适用场景:通常是最常用的选择,适合大部分情况,因为它能根据情况自动选择合适的连接方式。
2. 直接连接(Qt::DirectConnection)
描述:信号发出后立即调用槽函数,槽函数在信号发出者的线程中执行。
行为:信号与槽的调用顺序与代码顺序一致,即同步调用。
适用场景:适用于信号和槽在同一个线程的情况,或希望确保信号发出后立即执行槽。注意:如果信号和槽在不同线程中使用这种连接方式,可能会引发并发问题。
3. 队列连接(Qt::QueuedConnection)
描述:信号发出后,槽函数的调用会被放入接收者线程的事件队列中,槽函数会在接收者线程空闲时执行。
行为:信号和槽的调用是异步的,即信号发出后立即返回,槽函数的执行由事件循环调度。
适用场景:适用于信号和槽在不同线程的情况,能确保信号和槽调用的线程安全性。也适用于希望异步执行槽函数的场景。
4. 阻塞队列连接(Qt::BlockingQueuedConnection)
描述:这是队列连接的特殊形式,信号发出后将槽函数调用放入接收者线程的事件队列中,等待槽函数执行完毕后再返回。
行为:信号发出方会被阻塞,直到槽函数在接收者线程中执行完毕。
适用场景:仅在信号和槽不在同一个线程中时可用,常用于需要线程同步的场景。使用此连接时需谨慎,因为如果信号和槽在同一个线程中使用这种方式,程序会死锁。
5. 唯一连接(Qt::UniqueConnection)
描述:确保相同的信号和槽的连接只会存在一次,防止重复连接。
行为:如果尝试重复连接同一个信号和槽,将不会建立新的连接。
适用场景:适用于防止重复连接引发的逻辑错误或资源浪费情况。例如,有些信号可能会在特定条件下多次连接同一槽,使用唯一连接可以避免多次执行相同的槽。
智能指针:
1. QSharedPointer
用途:基于引用计数的共享所有权指针,多个指针可以共享同一对象,当最后一个引用被销毁时,对象自动释放。
特点:
类似std::shared_ptr,但提供更好的Qt框架集成。
支持自定义删除器(如QSharedPointer::CustomDeleter)。
线程安全(引用计数操作是原子的)。
示例:
QSharedPointer ptr1(new MyClass); QSharedPointer ptr2 = ptr1; // 引用计数增加 ptr2.clear();
// 引用计数减少,若为0则对象被删除
注意事项:
避免循环引用,否则会导致内存泄漏(可用QWeakPointer解决)。
不支持管理数组,需用QScopedArrayPointer或手动指定删除器。
2. QWeakPointer
用途:配合QSharedPointer使用,提供对共享对象的弱引用,不会增加引用计数,避免循环引用。
特点:
类似std::weak_ptr,需通过toStrongRef()提升为QSharedPointer访问数据。
可检查对象是否已被销毁。
示例:
QSharedPointer shared(new MyClass); QWeakPointer weak = shared; if (!weak.isNull()) {
QSharedPointer locked = weak.toStrongRef();
// 安全使用locked
}
3. QScopedPointer
用途:独占所有权的指针,对象在作用域结束时自动删除。
特点:类似std::unique_ptr,不可复制,但可通过移动转移所有权。
轻量级,无额外开销。
示例:
QScopedPointer scoped(new MyClass);
scoped->doSomething(); // 离开作用域时自动删除对象 }
操作:
reset():手动释放对象。
release():返回原始指针并放弃所有权。
注意事项:不适用于共享所有权场景。
支持自定义删除器(需通过模板参数指定)。
4. QScopedArrayPointer
用途:管理动态数组,析构时调用delete[]。
示例:
QScopedArrayPointer array(new int[100]);
array[0] = 42; // 支持[]操作符
5. QPointer
用途:弱引用QObject及其派生类,当对象被销毁时自动置为nullptr。
特点:不管理生命周期,仅观察对象是否存在。
适用于Qt对象(如窗口部件),避免访问已删除对象。
示例:
QObject* obj = new QObject;
QPointer qptr = obj;
delete obj;
if (qptr.isNull())
{
// 对象已被删除
}
6. QSharedDataPointer
用途:实现隐式共享(写时复制),用于需要高效共享数据的场景(如Qt容器类)。
示例:
class SharedData : public QSharedData
{
public:
int value;
};
class MyClass
{
public:
QSharedDataPointer data;
};
MyClass a;
a.data->value = 10;
MyClass b = a; // 数据共享,不复制
b.data->value = 20; // 写时复制,a和b数据分离
在Qt中使用智能指针时,需要注意以下关键事项,以确保代码的安全性和高效性:
1. 区分不同类型的智能指针
QSharedPointer基于引用计数,多个指针共享对象所有权。需注意循环引用问题,可能导致内存泄漏。配合QWeakPointer打破循环。
QScopedPointer作用域指针,离开作用域自动释放。不可复制,适合单一所有权场景。
QWeakPointer不持有对象所有权,需通过lock()转换为QSharedPointer才能访问对象。用于观察共享资源,避免循环引用。
2. 避免循环引用
当两个QSharedPointer相互引用时,引用计数无法归零,导致内存泄漏。 解决方案:将其中一个改为QWeakPointer,例如在父子对象或观察者模式中。
3. 与Qt对象树机制协同
Qt的传统父子对象机制会自动删除子对象。若对QObject使用智能指针:
避免同一对象同时由父对象树和智能指针管理,可能导致双重释放。
可使用QPointer跟踪QObject存活状态(类似QWeakPointer,但专为QObject设计)。
4. 线程安全性
QSharedPointer的引用计数操作是原子的,但对象本身的线程访问需手动同步(如加锁)。
QScopedPointer非线程安全,仅限单线程使用。
5. 自定义删除器
QSharedPointer支持运行时指定删除器,例如处理数组或特殊资源:
QSharedPointer ptr(new int[10], [](int *p)
{
delete[] p;
}
);
QScopedPointer删除器需通过模板参数指定,灵活性较低:
QScopedPointer> arr(new int[10]);
6. 避免混合使用不同智能指针
不要将同一原始指针交给多个智能指针(如QSharedPointer和std::shared_ptr),可能导致重复释放。
Qt与STL智能指针的删除逻辑可能不兼容,需统一管理策略。
7. 传递智能指针的参数
优先按const引用传递:避免不必要的引用计数增减,提升性能。
void process(const QSharedPointer& ptr);
需要传递所有权时,使用右值引用或std::move:
void takeOwnership(QSharedPointer&& ptr);
8. 数组的处理
使用QSharedPointer<T[]>或自定义删除器(如delete[]):
QSharedPointer arr(new int[100]);
9. 避免泄露原始指针
不要长期保存ptr.get()返回的裸指针,智能指针可能释放资源后,裸指针成为悬空指针。
在接口设计中优先使用智能指针,而非暴露原始指针。
10. 异常安全
智能指针在异常发生时能自动释放资源,但需确保在可能抛出异常的操作前完成资源获取。例如:
QSharedPointer res(new Resource); riskyOperation(); // 可能抛出异常,但res已安全持有资源
总结示例
// // 示例:避免循环引用
class A : public QObject {
public:
QWeakPointer<B> b_weak; // 使用QWeakPointer打破循环
};
class B : public QObject {
public:
QSharedPointer<A> a_ptr;
};
// 示例:自定义删除器
QSharedPointer<FILE> filePtr(fopen("data.txt", "r"), [](FILE *f) {
if (f) fclose(f);
});
// 示例:QObject与智能指针结合
QSharedPointer<QObject> obj = QSharedPointer<QObject>(new QObject);
QPointer<QObject> observer = obj.data(); // 安全观察对象状态
通过遵循这些注意事项,可以充分发挥Qt智能指针的优势,同时规避潜在的内存管理和设计陷阱。
GDB/LLDB: 在调试器中暂停程序(如Ctrl+C),检查各线程的堆栈信息,查看哪些线程在等待锁。
bash复制代码
gdb -p <进程ID> (gdb) thread apply all bt # 查看所有线程的堆栈