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版本利用模板推导指引,同时拥有类模板类型自动推导的特性,实现了直接构造的方法,不需要依赖工厂函数了。同时工厂函数使用万能引用,对于右值,可以减少一次拷贝,性能更优。