文章目录
代码
c
/*
* raii.h
*
*/
#ifndef COMMON_SOURCE_CPP_RAII_H_
#define COMMON_SOURCE_CPP_RAII_H_
#include <type_traits>
#include <functional>
#include <utility>
#include <cassert>
namespace gdface {
/*
* RAII方式管理申请和释放资源的类
* 对象创建时,执行acquire(申请资源)动作(可以为空函数[]{})
* 对象析构时,执行release(释放资源)动作
* 禁止对象拷贝和赋值
*/
class raii {
public:
using fun_type = std::function<void()>;
/* release: 析构时执行的函数
* acquire: 构造函数执行的函数
* default_com:_commit,默认值,可以通过commit()函数重新设置
*/
explicit raii(fun_type release, fun_type acquire = [] {}, bool default_com = true) :
_commit(default_com), _release(release) {
acquire();
}
/* 对象析构时根据_commit标志执行_release函数 */
~raii() noexcept {
if (_commit)
_release();
}
/* 移动构造函数 允许右值赋值 */
raii(raii&& rv)noexcept :_commit(rv._commit), _release(std::move(rv._release)) {
rv._commit = false;
};
/* 禁用拷贝构造函数 */
raii(const raii&) = delete;
/* 禁用赋值操作符 */
raii& operator=(const raii&) = delete;
/* 设置_commit标志 */
raii& commit(bool c = true)noexcept { _commit = c; return *this; };
private:
/* 为true时析构函数执行_release */
bool _commit;
protected:
/* 析构时执的行函数 */
std::function<void()> _release;
}; /* raii */
/* 用于实体资源的raii管理类
* T为资源类型
* acquire为申请资源动作,返回资源T
* release为释放资源动作,释放资源T
*/
template<typename T>
class raii_var {
public:
using _Self = raii_var<T>;
using resource_type = T;
using acq_type = std::function<T()>;
using rel_type = std::function<void(T&)>;
explicit raii_var(acq_type acquire, rel_type release) noexcept :
_resource(acquire()), _do_release(release) {
//构造函数中执行申请资源的动作acquire()并初始化resource;
}
/** 对于有默认构造函数的类型提供默认构造函数 */
template<typename _T = T, typename Enable = typename std::enable_if<std::is_default_constructible<_T>::value>::type>
raii_var()noexcept :_resource(), _need_release(false) {}
/* 对于有移动构造函数的类型提供移动构造函数 */
template<class _T = T>
raii_var(typename std::enable_if< std::is_object<_T>::value
&& !std::is_default_constructible<_T>::value
&& std::is_move_constructible<_T>::value, raii_var>::type&& rv)
: _resource(std::move(rv._resource)), _do_release(rv._do_release)
{
rv._need_release = false;//控制右值对象析构时不再执行_release
}
/* 对于只有复制构造函数的类型提供移动构造函数 */
template<class _T = T>
raii_var(typename std::enable_if< std::is_object<_T>::value
&& !std::is_default_constructible<_T>::value
&& !std::is_move_constructible<_T>::value
&& std::is_copy_constructible<_T>::value, raii_var>::type&& rv)
: _resource(rv._resource), _do_release(rv._do_release)
{
rv._need_release = false;//控制右值对象析构时不再执行_release
}
/* 对于指针和引用类型提供移动构造函数 */
template<class _T = T>
raii_var(typename std::enable_if< std::is_reference<_T>::value || std::is_pointer<_T>::value, raii_var>::type&& rv)
: _resource(rv._resource), _do_release(rv._do_release)
{
rv._need_release = false;//控制右值对象析构时不再执行_release
}
/* 对于有复制构造函数的类型提供移动赋值操作符(这个操作符似乎没什么用,考虑删除) */
template<typename _T = T, typename Enable = typename std::enable_if<std::is_copy_constructible<_T>::value>::type>
raii_var& operator=(raii_var&& rv) {
// 与右值对象(rv)交换所有成员变量,
// rv在析构的时候会根据_need_release标志正确释放当前对象原有的资源
std::swap(rv._resource, this->_resource);
std::swap(rv._do_release, this->_do_release);
std::swap(rv._need_release, this->_need_release);
return *this;
}
/* 对象析构时根据_commit标志执行_release函数 */
~raii_var() noexcept {
if (_need_release)
_do_release(_resource);
}
/* 设置_need_release标志 */
_Self& release(bool rel = true)noexcept { _need_release = rel; return *this; };
/* 设置_need_release标志为false,析构时不执行_release */
_Self& norelease()noexcept { return release(false); };
/* 获取资源引用 */
T& get() noexcept { return _resource; }
const T& get() const noexcept { return _resource; }
T& operator*() noexcept { return get(); }
const T& operator*() const noexcept { return get(); }
/* 标量类型提供()操作符 */
template<typename _T = T, typename Enable = typename std::enable_if<std::is_scalar<_T>::value>::type>
operator T () const noexcept { return _resource; }
/* 根据 T类型不同选择不同的->操作符模板 */
template<typename _T = T>
typename std::enable_if<std::is_pointer<_T>::value, _T>::type operator->() noexcept
{
return _resource;
}
template<typename _T = T>
typename std::enable_if<std::is_pointer<_T>::value, const _T>::type operator->() const noexcept
{
return _resource;
}
template<typename _T = T>
typename std::enable_if<std::is_class<_T>::value, const _T*>::type operator->() const noexcept
{
return std::addressof(_resource);
}
template<typename _T = T>
typename std::enable_if<std::is_class<_T>::value, _T*>::type operator->() noexcept
{
return std::addressof(_resource);
}
template<typename _T>
typename std::enable_if<!std::is_same<_T, T>::value&& std::is_class<_T>::value, _T&>::type _get() noexcept
{
return static_cast<_T&>(_resource);
}
template<typename _T>
typename std::enable_if<!std::is_same<_T, T>::value&& std::is_pointer<_T>::value, _T>::type _get() noexcept
{
return static_cast<_T>(_resource);
}
private:
/* 为true时析构函数执行release */
bool _need_release = true;
T _resource;
rel_type _do_release;
};
/* 创建 raii 对象,
* 用std::bind将M_REL,M_ACQ封装成std::function<void()>创建raii对象
* RES 资源类型
* M_REL 释放资源的成员函数地址
* M_ACQ 申请资源的成员函数地址
*/
template<typename RES, typename M_REL, typename M_ACQ>
inline raii make_raii(RES& res, M_REL rel, M_ACQ acq, bool default_com = true) {
// 编译时检查参数类型
// 静态断言中用到的is_class,is_member_function_pointer等是用于编译期的计算、查询、判断、转换的type_traits类,
// 有点类似于java的反射(reflect)提供的功能,不过只能用于编译期,不能用于运行时。
// 关于type_traits的详细内容参见:http://www.cplusplus.com/reference/type_traits/
static_assert(std::is_class<RES>::value, "RES is not a class or struct type.");
static_assert(std::is_member_function_pointer<M_REL>::value, "M_REL is not a member function.");
static_assert(std::is_member_function_pointer<M_ACQ>::value, "M_ACQ is not a member function.");
assert(nullptr != rel && nullptr != acq);
auto p_res = std::addressof(const_cast<typename std::remove_const<RES>::type&>(res));
return raii(std::bind(rel, p_res), std::bind(acq, p_res), default_com);
}
/* 创建 raii 对象 无需M_ACQ的简化版本 */
template<typename RES, typename M_REL>
inline raii make_raii(RES& res, M_REL rel, bool default_com = true) {
static_assert(std::is_class<RES>::value, "RES is not a class or struct type.");
static_assert(std::is_member_function_pointer<M_REL>::value, "M_REL is not a member function.");
assert(nullptr != rel);
auto p_res = std::addressof(const_cast<typename std::remove_const<RES>::type&>(res));
return raii(std::bind(rel, p_res), [] {}, default_com);
}
/* raii方式管理F(Args...)函数生产的对象
* 如果调用时指定T类型,则返回的RAII对象类型为T,否则类型为F(Args...)结果类型
*/
template<typename T = void, typename F, typename... Args,
typename ACQ_RES_TYPE = typename std::result_of<F(Args...)>::type,
typename TYPE = typename std::conditional<!std::is_void<T>::value && !std::is_same<T, ACQ_RES_TYPE>::value, T, ACQ_RES_TYPE>::type,
typename REL = std::function<void(TYPE&)> >
inline raii_var<TYPE>
raii_bind_var(REL rel, F&& f, Args&&... args) {
return raii_var<TYPE>(
[&]()->TYPE {return static_cast<TYPE>(std::bind(std::forward<F>(f), std::forward<Args>(args)...)()); },
rel);
}
} /* namespace gdface */
#endif /* COMMON_SOURCE_CPP_RAII_H_ */
示例
理解如何使用raii
和raii_var
类的关键在于了解它们是如何帮助你管理资源的生命周期的。让我们通过具体的例子来详细解释这两个类的使用方法。
raii
类的使用
假设我们需要在程序中打开一个文件,并确保无论程序执行过程中发生什么,文件都能被正确关闭。我们可以使用raii
类来管理这个过程。
示例代码:
cpp
#include <fstream>
#include <iostream>
#include "raii.h" // 假设你的RAII头文件名为raii.h
using namespace gdface;
void writeToFile(std::ofstream* file) {
if (file->is_open()) {
*file << "Hello, RAII!" << std::endl;
}
}
int main() {
std::ofstream* pFileStream = nullptr;
auto acquire = [&]() {
pFileStream = new std::ofstream("example.txt", std::ios::out);
};
auto release = [&]() {
if (pFileStream != nullptr) {
pFileStream->close();
delete pFileStream;
pFileStream = nullptr;
}
};
raii fileGuard(release, acquire);
writeToFile(pFileStream);
return 0;
}
解释:
acquire
函数:负责打开文件并分配内存。release
函数:负责关闭文件并释放内存。raii
对象fileGuard
:在其构造时调用acquire
打开文件,在析构时自动调用release
关闭文件。writeToFile
函数:使用已经打开的文件进行写操作。
raii_var
类的使用
raii_var
更加通用,可以直接持有资源类型(如文件流),并且可以方便地进行访问和操作。
示例代码:
假设我们同样需要管理一个文件流资源,但这次直接使用raii_var
来简化代码。
cpp
#include <fstream>
#include <iostream>
#include "raii.h" // 假设你的RAII头文件名为raii.h
using namespace gdface;
void writeToFile(raii_var<std::ofstream>& fileGuard) {
if (fileGuard->is_open()) {
(*fileGuard) << "Hello, RAII_var!" << std::endl;
}
}
int main() {
auto acquire = []() -> std::ofstream {
return std::ofstream("example.txt", std::ios::out);
};
auto release = [](std::ofstream& stream) {
if (stream.is_open()) {
stream.close();
}
};
raii_var<std::ofstream> fileGuard(acquire, release);
writeToFile(fileGuard);
return 0;
}
解释:
acquire
函数:返回一个新的文件流对象。release
函数 :接受一个文件流引用,并在其上调用close()
方法。raii_var
对象fileGuard
:在其构造时调用acquire
创建文件流,在析构时自动调用release
关闭文件流。writeToFile
函数 :使用fileGuard
提供的文件流进行写操作。
结合线程使用
如果你希望在多线程环境中使用这些RAII类来管理资源,可以通过将RAII对象传递给线程函数来实现。
示例代码:
cpp
#include <fstream>
#include <iostream>
#include <thread>
#include "raii.h" // 假设你的RAII头文件名为raii.h
using namespace gdface;
void threadFunc(raii_var<std::ofstream>& fileGuard) {
if (fileGuard->is_open()) {
(*fileGuard) << "Hello from Thread using RAII_var!" << std::endl;
}
}
int main() {
auto acquire = []() -> std::ofstream {
return std::ofstream("example.txt", std::ios::out);
};
auto release = [](std::ofstream& stream) {
if (stream.is_open()) {
stream.close();
}
};
raii_var<std::ofstream> fileGuard(acquire, release);
std::thread t(threadFunc, std::ref(fileGuard));
t.join(); // 等待线程完成
return 0;
}
解释:
std::thread
:启动一个新线程并传递fileGuard
的引用。std::ref(fileGuard)
:确保传递的是fileGuard
的引用而不是副本,这样线程可以共享同一个RAII对象。t.join()
:等待线程完成,确保主线程在子线程结束前不会退出。
通过这种方式,你可以确保无论是在单线程还是多线程环境下,资源都会被正确地获取和释放,避免了资源泄露的风险。
确实,代码中还定义了两个辅助函数 make_raii
和 raii_bind_var
,它们简化了创建 raii
和 raii_var
对象的过程。让我们详细解释这些辅助函数的用途和使用方法。
make_raii
函数
make_raii
是一个模板函数,用于简化 raii
对象的创建过程。它通过绑定类成员函数来自动创建 std::function<void()>
类型的资源获取和释放函数。
签名:
cpp
template<typename RES, typename M_REL, typename M_ACQ>
inline raii make_raii(RES & res, M_REL rel, M_ACQ acq, bool default_com = true);
- 参数 :
RES
: 资源类型。M_REL
: 释放资源的成员函数指针。M_ACQ
: 获取资源的成员函数指针。res
: 资源对象的引用。default_com
: 是否在析构时执行释放操作,默认为true
。
示例:
假设我们有一个类 ResourceManager
,其中包含打开和关闭文件的方法。
cpp
#include <fstream>
#include <iostream>
#include "raii.h" // 假设你的RAII头文件名为raii.h
using namespace gdface;
class ResourceManager {
public:
void openFile(const std::string& filename) {
file.open(filename, std::ios::out);
}
void closeFile() {
if (file.is_open()) {
file.close();
}
}
private:
std::ofstream file;
};
void writeToFile(ResourceManager& manager, const std::string& message) {
manager.openFile("example.txt");
if (manager.file.is_open()) {
manager.file << message << std::endl;
}
}
int main() {
ResourceManager manager;
auto guard = make_raii(manager, &ResourceManager::closeFile, &ResourceManager::openFile);
writeToFile(manager, "Hello from RAII!");
return 0;
}
解释:
make_raii
: 创建了一个raii
对象guard
,它会调用manager
的openFile
方法获取资源,并在析构时调用closeFile
方法释放资源。writeToFile
: 使用manager
对象进行写操作,确保文件在main
函数结束时会被正确关闭。
raii_bind_var
函数
raii_bind_var
是另一个模板函数,用于简化 raii_var
对象的创建。它可以绑定任意函数及其参数,并生成相应的资源管理对象。
签名:
cpp
template<typename T=void,typename F, typename... Args,
typename ACQ_RES_TYPE=typename std::result_of<F(Args...)>::type,
typename TYPE=typename std::conditional<!std::is_void<T>::value&&!std::is_same<T,ACQ_RES_TYPE>::value,T,ACQ_RES_TYPE>::type,
typename REL=std::function<void(TYPE&)> >
inline raii_var<TYPE>
raii_bind_var(REL rel,F&& f, Args&&... args);
- 参数 :
T
: 可选的资源类型,如果未指定,则默认为F(Args...)
的返回类型。F
: 资源获取函数。Args
: 资源获取函数的参数。rel
: 资源释放函数。
示例:
假设我们有一个简单的函数来创建一个文件流,并希望使用 raii_var
来管理这个文件流。
cpp
#include <fstream>
#include <iostream>
#include "raii.h" // 假设你的RAII头文件名为raii.h
using namespace gdface;
std::ofstream createFileStream(const std::string& filename) {
return std::ofstream(filename, std::ios::out);
}
void releaseFileStream(std::ofstream& stream) {
if (stream.is_open()) {
stream.close();
}
}
void writeToFile(std::ofstream& file, const std::string& message) {
if (file.is_open()) {
file << message << std::endl;
}
}
int main() {
auto fileGuard = raii_bind_var<std::ofstream>(
releaseFileStream,
createFileStream,
"example.txt"
);
writeToFile(fileGuard.get(), "Hello from RAII_var!");
return 0;
}
解释:
createFileStream
: 创建并返回一个新的std::ofstream
对象。releaseFileStream
: 接受一个std::ofstream
引用,并在其上调用close()
方法。raii_bind_var
: 创建了一个raii_var<std::ofstream>
对象fileGuard
,它会在构造时调用createFileStream
获取文件流,在析构时调用releaseFileStream
释放文件流。writeToFile
: 使用fileGuard
提供的文件流进行写操作。
总结
make_raii
: 简化了raii
对象的创建,适用于需要通过类成员函数管理资源的情况。raii_bind_var
: 简化了raii_var
对象的创建,适用于需要通过普通函数或可调用对象管理资源的情况。
这两个辅助函数的主要目的是减少样板代码,使资源管理更加简洁和安全。通过它们,你可以更方便地利用 RAII 模式来确保资源的正确获取和释放,无论是在单线程还是多线程环境中。