C++ Qt 临时文件处理的几种方式
本文主要介绍了 C++ Qt 中临时文件处理的几种方式,包括手动管理、RAII 等方式
💡注意:文中生成的示例代码均为 AI 生成并经过手动修改,仅供参考,实际使用时请根据实际情况进行调整。
1. 手动管理
最为原始的管理方式,手动创建临时文件,使用完再手动删除。足够简单也符合直觉的方式,当代码逻辑足够简单时,可以使用这种方式,一旦代码量增大,逻辑分支增多,不同的分支下可能会忘记删除临时文件,导致临时文件留存,占用磁盘空间。
#include <QFile>
#include <QDir>
void test1()
{
QString tempFile = QDir::tempPath() + "/temp.txt";
QFile file(tempFile);
if (file.open(QIODevice::WriteOnly))
{
file.write("Hello, World!");
file.close();
}
// do something
file.remove();
}
2. RAII
RAII(Resource Acquisition Is Initialization)是一种编程范式,其核心思想是在对象的构造函数中获取资源,在对象的析构函数中释放资源。这种方式可以保证资源的正确释放,不会因为异常等原因导致资源泄漏。
这里不讨论 RAII 的具体实现,只讨论如何使用 RAII 来管理临时文件的几种方式。
2.1. ScopedGuard
ScopedGuard 是一个简单的 RAII 类,用于管理资源的释放。ScopedGuard 的构造函数接受一个函数指针,当 ScopedGuard 对象析构时,会调用该函数指针。
ScopedGuard 的实现可以做到相当复杂且强大,这里只是一个简单的实现,用于演示如何使用 RAII 来管理临时文件。
C++ 未提供 ScopeGuard 的原生实现,可以使用各种第三方库,如 QScopeGuard 等。
#include <QFile>
#include <QDir>
class ScopedGuard
{
public:
ScopedGuard(std::function<void()> func) : m_func(func) {}
~ScopedGuard() { m_func(); }
private:
std::function<void()> m_func;
};
void test2()
{
QString tempFile = QDir::tempPath() + "/temp.txt";
QFile file(tempFile);
if (file.open(QIODevice::WriteOnly))
{
file.write("Hello, World!");
file.close();
}
ScopedGuard guard([&]() { file.remove(); }); // remove file when out of scope
// do something
}
2.2. 智能指针(推荐)
使用智能指针管理临时文件是一种非常好的方式,可以避免手动管理临时文件,也可以避免忘记删除临时文件的问题。
#include <QFile>
#include <QDir>
#include <memory>
class FileDeleter
{
public:
void operator()(QFile *file) const
{
file->remove();
delete file;
}
};
void test3()
{
QString tempFile = QDir::tempPath() + "/temp.txt";
QFile *file = new QFile(tempFile);
if (file->open(QIODevice::WriteOnly))
{
file->write("Hello, World!");
file->close();
}
// file will be removed when out of scope
std::unique_ptr<QFile, FileDeleter> filePtr(file);
// do something
}
上述代码使用了 std::unique_ptr
来管理临时文件,当 filePtr
对象析构时,会调用 FileDeleter
的 operator()
方法,从而删除临时文件。
实际上可以看出,FileDeleter
类的实现和 ScopedGuard
类的实现非常类似,只是 FileDeleter
类的 operator()
方法中删除了文件。
如果只是如此,则没有必要将其单独提取出来,直接在 std::unique_ptr
的第二个模板参数中传入一个 lambda 表达式即可。
#include <QFile>
#include <QDir>
#include <memory>
void test4()
{
QString tempFile = QDir::tempPath() + "/temp.txt";
QFile *file = new QFile(tempFile);
if (file->open(QIODevice::WriteOnly))
{
file->write("Hello, World!");
file->close();
}
std::unique_ptr<QFile, std::function<void(QFile *)>> filePtr(file, [](QFile *file) {
file->remove();
delete file;
});
}
通过上述代码,就得到了一个更加简洁的实现方式,这也是将智能指针单独列出来的目的,即提供一种极简的方式,在离开作用域时执行一些操作。
假设一个场景:某个函数的需要对特定数据进行各种检查,不同的检查失败的结果都需要执行统一或类似的操作,这时就可以使用智能指针来管理资源,当检查失败时,直接返回,智能指针会自动释放资源。
#include <QFile>
#include <QDir>
#include <memory>
void test5()
{
QString tempFile = QDir::tempPath() + "/temp.txt";
QFile *file = new QFile(tempFile);
if (!file->open(QIODevice::WriteOnly))
{
delete file;
return;
}
// file will be removed when out of scope
std::shared_ptr<void> finally(nullptr, [&](void*) { file->remove(); delete file; });
// do something
}
这里使用了
std::shared_ptr
,而不是std::unique_ptr
,因为std::unique_ptr
必须在构造时传入一个指针,而在目前遇到的部分场景中,可能并不希望传入一个指针,因此使用std::shared_ptr
更加灵活。
类似的编码范式在其他语言中也有所体现,如 Python 中的
with
语句,Java 中的try-with-resources
语句,C# 中的using
语句,Go 中的defer
语句,Rust 中的Drop
trait 等。
2.3. QScopeGuard(推荐)
💡需要注意的是:Qt 5.12 才引入了这个类
智能指针基于 C++11,不依赖于 Qt,因此可以在任何 C++ 项目中使用。这里再提供类似的,基于 QScopeGuard
的实现方式:
#include <QFile>
#include <QDir>
#include <QScopeGuard>
void test6()
{
QString tempFile = QDir::tempPath() + "/temp.txt";
QFile *file = new QFile(tempFile);
if (!file->open(QIODevice::WriteOnly))
{
delete file;
return;
}
// file will be removed when out of scope
auto cleanup = qScopeGuard([file] { file->remove(); delete file; });
// do something
}
可以看到,相比于
std::shared_ptr
,QScopeGuard
的使用更加简洁友好一些,但是需要依赖 Qt。
2.4. QTemporaryFile(推荐)
QTemporaryFile
是 Qt 提供的一个临时文件类,用于创建临时文件,当 QTemporaryFile
对象析构时,会自动删除临时文件。
实际应用场景:需要调用一个函数向临时文件中写入数据,后续又需要调用另一个函数对该临时文件进行加密。这时就可以使用 QTemporaryFile
来管理临时文件。
#include <QTemporaryFile>
void createFile(const QString &fileName)
{
// do something
writeSomethingToFile(fileName);
// do something
}
void encryptFile(const QString &fileName)
{
// do something
encryptFile(fileName);
// do something
}
void test7()
{
QTemporaryFile file;
if (file.open())
{
createFile(file.fileName());
}
encryptFile(file.fileName());
// do something
}
使用
QTemporaryFile
的另一个好处是,不需要关心文件的命名规则、文件名是否重复、文件的路径等问题。在上述代码中,只要QTemporaryFile
对象open
成功,就可以直接使用fileName
来获取自动生成的临时文件绝对路径。
总结
- 当你不关心文件的命名规则、文件名是否重复、文件的路径等问题时,使用
QTemporaryFile
- 当你需要在离开作用域时执行一些操作时,使用智能指针(
std::shared_ptr<void> finally(nullptr, [&](void*) { /* do something */ });
) - 当代码逻辑足够简单时,直接手动管理临时文件即可