QScopeGuard 源码分析
2023-3-2
| 2025-3-9
Words 1210Read Time 4 min
type
status
date
slug
summary
tags
category
icon
password

QScopeGuard 源码分析

QScopeGuard 是一种基于 RAII 机制实现的资源管理工具,类似 Go 语言的 defer 机制,能够在退出作用域时自动执行指定动作。 本文分析 Qt5.12(C++11)和 Qt6.5(C++17)的源码实现差异。

Qt5.12(C++11)版本

关键设计要点

1、工厂函数必要性

2、拷贝禁用与移动语义

3、值传递的安全性

代码如下

如上,如果我们想要构造一个defer对象,那么它的类型是什么呢? 是 QScopeGuard<decltype([](){})> ? 完全错误,因为 lambda 的会被编译器展开成一个结构体,它的类型是唯一的。那么只能通过一个模板函数进行类型推导了,所以就有了上述的 QScopeGuard<F> qScopeGuard(F f)

Qt6.5(C++17)版本改进

关键改进点

1、模板参数推导指引(CTAD)

2、万能引用与完美转发

关键问题解答

相比于 C++11 版本,该版本的 qScopeGuard 考虑的非常周全,其中有几个地方非常值得细品。

1、为什么 qScopeGuard(F &&f) 使用了 std::decay<F>::type

decay 在这里的主要作用是将函数类型以及函数引用类型退化至函数指针 为啥要移除引用呢?其实很简单,首先,因为如果传递的参数为引用的话,那么QScopeGuard内部的m_func会被推导成函数引用,那m_func的生命周期将和拥有它的QScopeGuard不一致, 也就是说传递的参数已经离开作用域了,这时候QScopeGuard内部的m_func将会指向一个悬垂引用(Dangling reference)。

2、template QScopeGuard(F(&)()) -> QScopeGuard<F(*)()>;

template 这其实是C++17的新特性: 模板推导指引。这个语句的作用是提示编译器,对于函数引用类型,都推导成函数指针类型。为什么有了 decay 了,还需要这个呢?其实是为了不使用 qScopeGuard 而是直接构造的时候用的。

总结

1、实现一个 ScopeGuard 的关键,是需要存储各式各样的”函数”,如函数指针、lambda以及functor。
2、C++11版本中,利用一个模板工厂函数,解决类模板无法自动推导类型的问题。但是由于C++11不支持std::decay以及模板推导指引, 工厂函数的参数采用值类型传递,中间会进行拷贝操作,性能较差。但也解决了悬垂引用的问题。
3、C++17版本利用模板推导指引,同时拥有类模板类型自动推导的特性,实现了直接构造的方法,不需要依赖工厂函数了。同时工厂函数使用万能引用,对于右值,可以减少一次拷贝,性能更优。
Qt 核心机制源码分析之元对象系统Qt源码剖析之事件循环系统
Loading...