способ добавить cleanup-code в легаси код

117
07 ноября 2021, 08:50

Есть условная легаси функция (2к строк) с кучей точек завершения.

long foo(...)
{
    if (statement1)
        return -1;
    if (statement2)
        return -2;
    for (...)
    {
        if(...)
            return -3;
        ...
    }
    ...
}

Функция модифируется и возникает необходимость перед каждым return вызвать определенный cleanup код, обернуть этот код в локальный объект, который бы автоматически разрушался при завершении функции не получается.
Вариант обернуть тело функции в try-catch наверное не подходит т.к. функция частично перебрасывает исключения наверх, частично обрабатывает своими силами и наверняка будет некий сайд-эффект от такого способа.

Есть ли способы сделать это без дописывания cleanup-code к каждому return?

дело под виндой, vs2015

P.S. пока придумалось вместо каждого return вызывать лямбду/функцию, которая будет вызывать cleanup-code и делать return

Answer 1

Вам нужен scope guard.

Работает это примерно так:

FILE *f = std::fopen(...);
if (!f) throw ...;
FINALLY( std::fclose(f); )
// Тут используется `f`.

Здесь FINALLY - это макрос, тот самый scope guard.

Код внутри scope guard вызывается не сразу, а после выхода из области видимости (у вас - после выхода из функции).

Реализацию scope guard можно нагуглить, а можно написать свою (это несложно).

Я обычно использую вот такой вариант:

namespace Macro
{
    template <typename T> class FinallyObject
    {
        T func;
      public:
        FinallyObject(T &&func) : func(std::move(func)) {}
        FinallyObject(const FinallyObject &) = delete;
        FinallyObject &operator=(const FinallyObject &) = delete;
        ~FinallyObject()
        {
            func();
        }
    };
}
#define FINALLY_impl_cat(a, b) FINALLY_impl_cat_(a, b)
#define FINALLY_impl_cat_(a, b) a##b
#define FINALLY(...) \
    ::Macro::FinallyObject FINALLY_impl_cat(_finally_object_,__LINE__) \
    ([&]{ __VA_ARGS__ });
// Как обычный FINALLY, но выполняется только при выбросе исключения.    
#define FINALLY_ON_THROW(...) \
    ::Macro::FinallyObject FINALLY_impl_cat(_finally_object_,__LINE__) \
    ([&, _finally_exc_depth_ = ::std::uncaught_exceptions()] \
    { if (::std::uncaught_exceptions() != _finally_exc_depth_) {__VA_ARGS__} });
// Наоборот, выполняется если вызов деструктора произошел из-за нормального
// выхода из scope, а не из-за исключения.
#define FINALLY_ON_SUCCESS(...) \
    ::Macro::FinallyObject FINALLY_impl_cat(_finally_object_,__LINE__) \
    ([&, _finally_exc_depth_ = ::std::uncaught_exceptions()] \
    { if (::std::uncaught_exceptions() == _finally_exc_depth_) {__VA_ARGS__} });

Вот переделанная версия, которая работает без С++17.

Предупреждение: В этом варианте мы вынуждены использовать std::uncaught_exception вместо std::uncaught_exceptions, поэтому в некоторых редких случаях обнаружение исключений (макросы FINALLY_ON_THROW и FINALLY_ON_SUCCESS) будут работать некорректно.

Например, если они используются внутри деструктора (в том числе в любой вызванной им функции), и этот деструктор был вызван из-за исключения, то макросы FINALLY_ON_THROW и FINALLY_ON_SUCCESS будут учитывать это исключение (первый вызовет свой код, а второй - нет). Это - неправильное поведение, так как учитываться должны только новые исключения (выброшенные после прохода выполнения через макрос).

namespace Macro
{
    template <typename T> class FinallyObject
    {
        T func;
      public:
        FinallyObject(T &&func) : func(std::move(func)) {}
        FinallyObject(FinallyObject &&) = default;
        FinallyObject &operator=(const FinallyObject &) = delete;
        ~FinallyObject()
        {
            func();
        }
    };
    template <typename T>
    FinallyObject<T> MakeFinallyObject(T &&func)
    {
        return {std::move(func)};
    }
}
#define FINALLY_impl_cat(a, b) FINALLY_impl_cat_(a, b)
#define FINALLY_impl_cat_(a, b) a##b
#define FINALLY(...) \
    auto FINALLY_impl_cat(_finally_object_,__LINE__) = ::Macro::MakeFinallyObject([&]{ __VA_ARGS__ });
// Как обычный FINALLY, но выполняется только при выбросе исключения.
#define FINALLY_ON_THROW(...) \
    FINALLY( if (::std::uncaught_exception()) {__VA_ARGS__} )
// Наоборот, выполняется если вызов деструктора произошел из-за нормального
// выхода из scope, а не из-за исключения.
#define FINALLY_ON_SUCCESS(...) \
    FINALLY( if (!::std::uncaught_exception()) {__VA_ARGS__} )
READ ALSO
Цикличное выполнение ajax на click

Цикличное выполнение ajax на click

Есть вот такой вызов ajax, который работает по клику на option

162
Как правильно написать игру - угадай число [закрыт]

Как правильно написать игру - угадай число [закрыт]

Хотите улучшить этот вопрос? Добавьте больше подробностей и уточните проблему, отредактировав это сообщение

178