Как обернуть все вызовы API единым обработчиком ошибок?

Я имею дело с C-библиотекой, предоставляемой поставщиком (назовем ее foo), которая предлагает множество вызовов API. Все эти функции принимают (предварительно инициализированный) дескриптор (типа FOOHandle) и возвращают 0 в случае успеха и какое-то другое число в случае неудачи. В последнем случае вызывающий абонент может вызвать foo_get_errmsg(), чтобы получить текстовое описание ошибки, сохраненное в его собственном буфере. Ничего особенного.

Программируя на C++, я хотел бы превратить эти ошибки в исключения моего типа. С этой целью я создал свой собственный класс FOOException со следующим конструктором:

private:
    char    buf[1024];
    int     code;
public:
    FOOException(FOOHandle h, int32_t _code) noexcept : code(_code)
    {
        foo_get_errmsg(code, h, buf);
    }

Все идет нормально.

Чтобы помочь преобразовать существующий код, я хотел бы добавить общую функцию-оболочку, которая будет вызывать указанный вызов API, проверять его результат, а затем выдавать новое исключение, если это необходимо:

    static auto wrapper(const auto &foofunc, auto&&... args) const
    {
        auto code = foofunc(args...);

        switch (code) {
        case 0:
            return 0;
        default:
            throw FOOException(handle, code);
        }
    }

Моя проблема в том, чтобы получить это handle... Хотя аргумент всегда присутствует во всех вызовах FOO-функций - поэтому всегда среди args... - он не всегда первый:

        foo_analyze(a, handle);
...
        foo_meow(handle, a, b, c, d);

Есть ли какой-нибудь способ «выловить» дескриптор из args... (по имени или по типу), чтобы его можно было использовать и в оболочке, или я должен дублировать его во всех вызовах wrapper() — один раз для самой оболочки, и с другой стороны, для оборачиваемой функции?

Как насчет пространства имен-оболочки, которое упорядочивает дескриптор, чтобы он всегда был первым аргументом. Введите namespace std_foo, а затем analyze(), то есть analyze(handle,a), это немного усилий (хотя при встроенной компиляции не будет никаких накладных расходов), и давайте посмотрим правде в глаза API, где дескриптор, который является первым аргументом, немного эксцентричен, не так ли?

Persixty 03.09.2024 20:58

Кажется, что проблемная область на самом деле шире, чем вопрос. Сначала я бы предоставил оболочку RAII для дескрипторов; обычно они могут быть только подвижными или неподвижными. Функции, возвращающие коды ошибок, обычно возвращают фактический результат в виде аргумента-указателя; если это так, я бы предоставил функции-оболочки, которые возвращают std::expected или сторонний эквивалент (boost::outcome) с int в качестве типа ошибки. Традиционная обработка исключений — использование try+catch — больше не является канонической тенденцией. Монадические классы являются рекомендуемым решением.

Red.Wave 03.09.2024 21:58
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
2
106
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Предполагая, что из FooHandle есть только один Args, вы можете сделать что-то вроде (создать std::tuple и извлечь по типу):

auto& handle = std::get<FooHandle&>(std::tie(args...));

Демо

Отлично... И компилятору хватило бы ума понять это во время компиляции, не так ли?

Mikhail T. 03.09.2024 19:14

Да, это время компиляции.

Jarod42 03.09.2024 19:16

дескрипторы в C обычно являются int и void*, могут конфликтовать с другими параметрами

Gene 03.09.2024 20:31

Хорошая мысль, @Gene. В этой библиотеке это void *. Хуже того, это #define-d как таковой, даже не typedef. Откуда мне знать о конфликте — будет ли это ошибкой компилятора или он может просто молча выбрать другой указатель?

Mikhail T. 03.09.2024 20:35

@МихаилТ. Если FOOHandle не является уникальным типом (даже typedef — это просто псевдоним, а не тип), то это значительно усложняет задачу, если не делает ее невозможной, если какая-либо из функций API использует void* для других целей. Возможно, вам придется определить свой собственный тип, чтобы обернуть любой FOOHandle, который вы передаете, в свой wrapper, чтобы вы могли его идентифицировать.

Remy Lebeau 03.09.2024 20:36

Я думаю, что это единственный void *, но мне бы хотелось, чтобы компилятор сказал мне, если я ошибаюсь. Будет ли это? Или он незаметно отберет первый void * у args...?

Mikhail T. 03.09.2024 20:48

@МихаилТ. std::tie() возвращает std::tuple, тип которого может повторяться несколько раз. Однако std::get() не удастся скомпилировать, если вы запросите повторяющийся тип.

Remy Lebeau 03.09.2024 21:07

Вы можете вручную распаковать параметры шаблона, пока не найдете дескриптор, например:

FOOHandle getfoohandle() {
    return nullptr; // or whatever constitutes an "invalid" handle...
}

FOOHandle getfoohandle(FOOHandle h, auto&&...) {
    return h;
}

FOOHandle getfoohandle(auto&&, auto&&... args) {
    return getfoohandle(std::forward<decltype(args)>(args)...);
}

static void wrapper(const auto &foofunc, auto&&... args) const
{
    auto code = foofunc(std::forward<decltype(args)>(args)...);
    if (code != 0) {
        FOOHandle handle = getfoohandle(std::forward<decltype(args)>(args)...);
        throw FOOException(handle, code);
    }
}

Онлайн-демо


ОБНОВЛЕНИЕ: для компиляторов до C++20 auto не разрешено в параметрах функции, поэтому вместо этого вам придется использовать template параметры, например:

FOOHandle getfoohandle() {
    return nullptr; // or whatever constitutes an "invalid" handle...
}

template<typename... Args>
FOOHandle getfoohandle(FOOHandle h, Args&&...) {
    return h;
}

template<typename Param1, typename... Args>
FOOHandle getfoohandle(Param1&&, Args&&... args) {
    return getfoohandle(std::forward<Args>(args)...);
}

template<typename Func, typename... Args>
static void wrapper(const Func &foofunc, Args&&... args) const
{
    auto code = foofunc(std::forward<Args>(args)...);
    if (code != 0) {
        FOOHandle handle = getfoohandle(std::forward<Args>(args)...);
        throw FOOException(handle, code);
    }
}

Онлайн-демо

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

Gene 03.09.2024 20:33

@Gene, это была копия/вставка исходного кода ОП. Я исправил это в своем примере, но вместо этого вам следует направить комментарий в ОП.

Remy Lebeau 03.09.2024 20:35

Эх, пытаюсь скомпилировать свою новую оболочку с std=c++17, получаю error: 'auto' not allowed in function prototype. Использование C++20 мне подходит, но у моих коллег, использующих Windows, этого нет в компиляторе VС++ :(

Mikhail T. 03.09.2024 20:46

@МихаилТ. auto параметров еще не существовало в C++17. MSVC поддерживает C++20, начиная с VS 2019 v16.11 с переключателем /std:c++20. Для компиляторов до C++20 вместо этого вам придется использовать параметры template. Я обновил свой ответ, чтобы показать это.

Remy Lebeau 03.09.2024 20:55

У меня это работало для лямбда-функции раньше...

Mikhail T. 03.09.2024 20:58

@МихаилТ. Однако лямбды не являются функциями. auto для лямбда-параметров было добавлено в C++14, а auto для параметров функции было добавлено в C++20.

Remy Lebeau 03.09.2024 21:05

Кажется, даже с С++ 17 работает следующее: static const auto wrapper = [](const auto &func, auto&&... args)...

Mikhail T. 03.09.2024 21:26

Другие вопросы по теме