如何在函数出口上运行清理代码?

时间:2018-02-17 15:17:23

标签: c++ exception exception-handling raii exception-safety

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);
}

即使您使用带有RAII习语的C ++类,有时您也必须编写具有强大异常安全保证的代码:

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
}

如果可以创建这样的功能,有没有理由避免使用它们?其他编程语言中是否有这样的结构?

3 个答案:

答案 0 :(得分:0)

我创建了实现此功能的宏。它们生成一个局部变量,使用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

答案 1 :(得分:0)

C ++有析构函数,这是你需要的。一个对象,它在析构函数的作用域中执行你需要做的任何事情,然后在你需要完成工作的作用域中创建一个堆栈实例,当你保留作用域时会被销毁,然后在那个时候做工作

答案 2 :(得分:0)

ScopeGuard是您的正确选择。它基本上会调用您在析构函数中指定的函数。

因此您的代码可以是:

void your_function() {
  scope_guard guard = [&vector]() {
    vector.pop_back();
  };
  // your code
  guard.dismiss(); // success
}