最近改了自己的三个代码 Bug 都是任务终止时资源清理出了问题,要么任务取消后没删除缓存文件并返回 false,要么当前步骤判断任务取消后又在下一步流程继续执行了。为什么不判断取消后就直接 return 呢?因为还有一些清理工作,然后还需要把状态传递给其他对象。
减少这种逻辑上的失误,还是得从代码设计上着手。目前能想到的有 scopeguard、goto(C++ 异常没有 final ,不然也可以算上)。正好 Qt 提供了 QScopeGuard 可以学习下,虽然原理很简单,就是 guard 对象析构时执行我们的 lambda 函数。
QScopeGuard 的主要作用就是在作用域结束时执行一段资源释放逻辑。
QScopeGuard 使用方法:
void func()
{
char *buf = new char[1024];
auto cleanup = qScopeGuard([buf]{
//scopeguard对象释放时,析构函数种执行该逻辑
delete []buf;
});
Q_UNUSED(cleanup)
//业务逻辑
}
由于 scopeguard 实现很简单,可以学习下 QScopeGuard 的源码(Qt 5.15 版本):
#include <QtCore/qglobal.h>
#include <type_traits>
#include <utility>
QT_BEGIN_NAMESPACE
template <typename F>
class
#if __has_cpp_attribute(nodiscard)
// Q_REQUIRED_RESULT can be defined as __warn_unused_result__ or as [[nodiscard]]
// but the 1st one has some limitations for example can be placed only on functions.
Q_REQUIRED_RESULT
#endif
QScopeGuard
{
public:
explicit QScopeGuard(F &&f) noexcept
: m_func(std::move(f))
{
}
explicit QScopeGuard(const F &f) noexcept
: m_func(f)
{
}
QScopeGuard(QScopeGuard &&other) noexcept
: m_func(std::move(other.m_func))
, m_invoke(qExchange(other.m_invoke, false))
{
}
~QScopeGuard() noexcept
{
if (m_invoke)
m_func();
}
void dismiss() noexcept
{
m_invoke = false;
}
private:
Q_DISABLE_COPY(QScopeGuard)
F m_func;
bool m_invoke = true;
};
#ifdef __cpp_deduction_guides
template <typename F> QScopeGuard(F(&)()) -> QScopeGuard<F(*)()>;
#endif
//! [qScopeGuard]
template <typename F>
#if __has_cpp_attribute(nodiscard)
Q_REQUIRED_RESULT
#endif
QScopeGuard<typename std::decay<F>::type> qScopeGuard(F &&f)
{
return QScopeGuard<typename std::decay<F>::type>(std::forward<F>(f));
}
QT_END_NAMESPACE
该类是一个模板类,因为在析构函数中使用了括号进行调用,可知模板参数必须为一个可调用对象,一般是个 lambda 函数。类在构造时传入可调用对象,在释放时回调该对象。
dismiss() 接口将调用标志置为 false,在析构时判断该标志为 false 就不再处理可调用对象。
小写 q 开头的 qScopeGuard 是一个便捷函数,用来生成一个 QScopeGuard 对象。
nodiscard attribute 是 C++17 新增的属性。如果调用者丢弃了函数的返回值则编译器会报警告,因为需要一个栈变量保存 qScopeGuard 函数生成的对象,所以不能丢弃返回值。
deduction guides (推断指引)也是 C++17 新增的。当我们想要通过类模板新建一个类时,希望编译器能够通过我们给定的规则创建与构造器传入参数对应的模板类型时。我们给定的规则就叫做推断指引。当模板名称显示为推导类类型的类型说明符时,使用推断指引。
#include <type_traits>
#include <iostream>
#include <string>
template<typename T>
class A
{
public:
A(T const& t):s(t) {}
T& show() { return s; }
private:
T s;
};
// 推断指引
A(const char*)->A<std::string>;
A(bool)->A<int>;
A(int)->A<char>;
int main()
{
A Int{40}; //input int
std::cout << typeid(Int.show()).name() << std::endl; //output char
A Bool{true}; //input bool
std::cout << typeid(Bool.show()).name() << std::endl; //output int
A str{"hello"}; //input char[]
std::cout << typeid(str.show()).name() << std::endl; //output std::string
}
C++11 std::decay 对类型应用左值到右值、数组到指针及函数到指针隐式转换,移除 cv 限定符,并定义结果类型为成员 typedef type。
C++11 std::forward 完美转发保留参数的左值右值特性。
(模板元这个处理还得再学习一下,暂略)