c-如何在函数出口上运行清除代码?
作者:互联网
C类提供RAII习惯用法.因此,您不必关心异常:
void function()
{
// The memory will be freed automatically on function exit
std::vector<int> vector(1000);
// Do some work
}
但是,如果(出于某些原因)必须使用某些纯C API,则必须围绕它创建C包装程序,或者使用try / catch块
void function()
{
int *arr = (int*)malloc(1000*sizeof(int));
if (!arr) { throw "cannot malloc"; }
try
{
// Do some work
}
catch (...)
{
free(arr); // Free memory in case of exception
throw; // Rethrow the exception
}
// Free memory in case of success
free(arr);
}
即使您将C类与RAII习惯用法一起使用,有时也必须编写具有强大异常安全保证的代码:
void function(std::vector<const char*> &vector)
{
vector.push_back("hello");
try
{
// Do some work
vector.push_back("world");
try
{
// Do other work
}
catch (...)
{
vector.pop_back(); // Undo vector.push_back("world")
throw; // Rethrow the exception
}
}
catch (...)
{
vector.pop_back(); // Undo vector.push_back("hello");
throw; // Rethrow the exception
}
}
但是这些结构相当庞大.
有什么方法可以强制在函数出口处运行一些清理代码?类似于atexit,但在功能范围内…
有什么方法可以在不使用嵌套try / catch块的情况下在异常情况下运行一些回滚代码?
我想要一些像这样的运算符或函数:
void function(std::vector<const char*> &vector)
{
int *arr = malloc(1000*sizeof(int));
onexit { free(arr); }
vector.push_back("hello");
onexception { vector.pop_back(); }
// Do some work
vector.push_back("world");
onexception { vector.pop_back(); }
// Do other work
}
如果可以创建这样的功能,是否有任何避免使用它们的理由?其他编程语言中是否有这样的构造?
解决方法:
我已经创建了实现此功能的宏.它们生成局部变量,该局部变量使用C 11 lambda函数在析构函数中运行清除代码. std :: uncaught_exception函数用于检查当前是否存在任何异常.创建变量本身不应该引发任何异常,因为使用具有被引用捕获的所有变量的lambda来创建变量(此类lambda在复制/移动构造函数中不会引发异常).
#include <exception>
// An object of the class below will run an arbitrary code in its destructor
template <bool always, typename TCallable>
class OnBlockExit
{
public:
TCallable m_on_exit_handler;
~OnBlockExit()
{
if (always || std::uncaught_exception())
{ m_on_exit_handler(); }
}
};
// It is not possible to instantiate an object of the 'OnBlockExit' class
// without using the function below: https://stackoverflow.com/a/32280985/5447906.
// Creating of an object of the 'OnBlockExit' class shouldn't throw any exception,
// if lambda with all variables captured by reference is used as the parameter.
template <bool always, typename TCallable>
OnBlockExit<always, TCallable> MakeOnBlockExit(TCallable &&on_exit_handler)
{
return { std::forward<TCallable>(on_exit_handler) };
}
// COMBINE is needed for generating an unique variable
// (the name of the variable contains the line number:
// https://stackoverflow.com/a/10379844/544790)
#define COMBINE1(X,Y) X##Y
#define COMBINE(X,Y) COMBINE1(X,Y)
// ON_BLOCK_EXIT generates a variable with the name
// in the format on_block_exit##__LINE__
#define ON_BLOCK_EXIT(always, code) \
auto COMBINE(on_block_exit,__LINE__) = MakeOnBlockExit<always>([&]()code)
// Below are target macros that execute the 'code' on the function exit.
// ON_FINALLY will allways execute the code on the function exit,
// ON_EXCEPTION will execute it only in the case of exception.
#define ON_EXCEPTION(code) ON_BLOCK_EXIT(false, code)
#define ON_FINALLY(code) ON_BLOCK_EXIT(true , code)
这是一个如何使用这些宏的示例:
void function(std::vector<const char*> &vector)
{
int *arr1 = (int*)malloc(800*sizeof(int));
if (!arr1) { throw "cannot malloc arr1"; }
ON_FINALLY({ free(arr1); });
int *arr2 = (int*)malloc(900*sizeof(int));
if (!arr2) { throw "cannot malloc arr2"; }
ON_FINALLY({ free(arr2); });
vector.push_back("good");
ON_EXCEPTION({ vector.pop_back(); });
auto file = fopen("file.txt", "rb");
if (!file) { throw "cannot open file.txt"; }
ON_FINALLY({ fclose(file); });
vector.push_back("bye");
ON_EXCEPTION({ vector.pop_back(); });
int *arr3 = (int*)malloc(1000*sizeof(int));
if (!arr3) { throw "cannot malloc arr3"; }
ON_FINALLY({ free(arr3); });
arr1[1] = 1;
arr2[2] = 2;
arr3[3] = 3;
}
所有清除代码均以相反的顺序执行(其顺序与函数中ON_FINALLY / ON_EXCEPTION宏出现的顺序相反).仅当控制权超出相应的ON_FINALLY / ON_EXCEPTION宏时,才执行清除代码.
检查以下链接以查看演示程序执行的输出:http://coliru.stacked-crooked.com/a/d6defaed0949dcc8
标签:raii,c,exception,exception-handling,exception-safety 来源: https://codeday.me/bug/20191013/1907792.html