“现代C++ RAII库:设计、优化及实战应用“

文章目录

代码

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_ */

示例

理解如何使用raiiraii_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;
}
解释:
  1. acquire 函数:负责打开文件并分配内存。
  2. release 函数:负责关闭文件并释放内存。
  3. raii 对象 fileGuard :在其构造时调用acquire打开文件,在析构时自动调用release关闭文件。
  4. 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;
}
解释:
  1. acquire 函数:返回一个新的文件流对象。
  2. release 函数 :接受一个文件流引用,并在其上调用close()方法。
  3. raii_var 对象 fileGuard :在其构造时调用acquire创建文件流,在析构时自动调用release关闭文件流。
  4. 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;
}
解释:
  1. std::thread :启动一个新线程并传递fileGuard的引用。
  2. std::ref(fileGuard) :确保传递的是fileGuard的引用而不是副本,这样线程可以共享同一个RAII对象。
  3. t.join():等待线程完成,确保主线程在子线程结束前不会退出。

通过这种方式,你可以确保无论是在单线程还是多线程环境下,资源都会被正确地获取和释放,避免了资源泄露的风险。

确实,代码中还定义了两个辅助函数 make_raiiraii_bind_var,它们简化了创建 raiiraii_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;
}
解释:
  1. make_raii : 创建了一个 raii 对象 guard,它会调用 manageropenFile 方法获取资源,并在析构时调用 closeFile 方法释放资源。
  2. 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;
}
解释:
  1. createFileStream : 创建并返回一个新的 std::ofstream 对象。
  2. releaseFileStream : 接受一个 std::ofstream 引用,并在其上调用 close() 方法。
  3. raii_bind_var : 创建了一个 raii_var<std::ofstream> 对象 fileGuard,它会在构造时调用 createFileStream 获取文件流,在析构时调用 releaseFileStream 释放文件流。
  4. writeToFile : 使用 fileGuard 提供的文件流进行写操作。

总结

  • make_raii : 简化了 raii 对象的创建,适用于需要通过类成员函数管理资源的情况。
  • raii_bind_var : 简化了 raii_var 对象的创建,适用于需要通过普通函数或可调用对象管理资源的情况。

这两个辅助函数的主要目的是减少样板代码,使资源管理更加简洁和安全。通过它们,你可以更方便地利用 RAII 模式来确保资源的正确获取和释放,无论是在单线程还是多线程环境中。

相关推荐
zhglhy2 分钟前
springboot主要有哪些功能
java·spring boot·后端
机构师19 分钟前
<tauri><rust><GUI>基于rust和tauri,在已有的前端框架上手动集成tauri示例
开发语言·javascript·rust·前端框架·tauri
S-X-S21 分钟前
Java面试题-计算机网络
java·开发语言·计算机网络
idealmu31 分钟前
windows + visual studio 2019 使用cmake 编译构建静、动态库并调用详解
c++·windows·visual studio
vir0240 分钟前
P3654 First Step (ファーストステップ)(贪心算法)
数据结构·c++·算法
Dolphin_Home42 分钟前
使用 CMake 自动管理 C/C++ 项目
c语言·c++·cmake
学徒小新1 小时前
(六)C++的函数模板与类模板
java·c++·算法
望外追晚1 小时前
简单的回调函数理解
开发语言·c++
偷光1 小时前
React受控组件的核心原理与实战精要
前端·javascript·react.js
~怎么回事啊~1 小时前
c++ template-3
开发语言·c++