Элегантная попытка выполнить различные функции определенным образом - PullRequest
0 голосов
/ 06 февраля 2019

Я пытаюсь выполнять различные функции последовательно n раз, двигаясь вперед, только если предыдущая функция не вернула false (ошибка), в противном случае я перезагружаюсь и начинаю все заново.

Пример последовательностибудет:

  1. Включить модуль: module.power(true), 3 попытки
  2. Ожидание сигнала: module.signal(), 10 попыток
  3. Отправить сообщение: module.sendSMS('test'), 3 попытки
  4. Выключить модуль: module.power(false), 1 попытка

Каждое из этих действий выполняется одинаково, изменяя только текст DEBUG и функцию для запуска:

DEBUG_PRINT("Powering ON");  // This line changes
uint8_t attempts = 0;
uint8_t max_attempts = 3;  // max_attempts changes
while(!module.power(true) && attempts < max_attempts){  // This line changes
  attempts++;
  DEBUG_PRINT(".");
  if(attempts == max_attempts) {
    DEBUG_PRINTLN(" - Failed.");
    soft_reset();  // Start all over again
  }
  delay(100);
}
DEBUG_PRINTLN(" - Success");
wdt_reset(); // Reset watchdog timer, ready for next action

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

void try_this_action(description, function, n_attempts)

Что бы сделать действия 1-4 выше, например:

try_this_action("Powering ON", module.power(true), 3);
try_this_action("Waiting for signal", module.signal(), 10);
try_this_action("Sending SMS", module.sendSMS('test'), 3);
try_this_action("Powering OFF", module.power(false), 1);

Трудность, с которой я столкнулся, заключается в том, что вызываемые функции имеют разный синтаксис (некоторые принимают параметры, другие - нет ...),Есть ли более элегантный способ сделать это, кроме копирования / вставки фрагмента кода везде, где мне это нужно?

Ответы [ 4 ]

0 голосов
/ 06 февраля 2019

Вы можете использовать переменную функцию, объявив сначала в списке параметров те параметры, которые всегда присутствуют, затем переменную часть.В следующем коде мы определяем тип для функций действия, void, возвращая в качестве параметра список аргументов:

typedef void (*action)(va_list);

Затем определяем общую процедуру действия, которая готовится к выполнению действия:

void try_this_action(char *szActionName, int trials, action fn_action, ...)
{
    va_list args;
    va_start(args, fn_action);    //Init the argument list

    DEBUG_PRINT(szActionName);  // This line changes
    uint8_t attempts = 0;
    uint8_t max_attempts = trials;  // max_attempts changes
    //Here we call our function through the pointer passed as argument
    while (!fn_action(args) && attempts < max_attempts)
    {   // This line changes
        attempts++;
        DEBUG_PRINT(".");
        if (attempts == max_attempts)
        {
            DEBUG_PRINTLN(" - Failed.");
            soft_reset();   // Start all over again
        }
        delay(100);
    }
    DEBUG_PRINTLN(" - Success");
    wdt_reset();    // Reset watchdog timer, ready for next action
    va_end(args);
}

Каждая функция должна быть закодирована для использования списка аргументов:

int power(va_list args)
{
    //First recover all our arguments using the va_arg macro
    bool cond = va_arg(args, bool);

    if (cond == true)
    {
        ... //do something
            return true;
    }
    return false;
}

Использование будет:

try_this_action("Powering ON", 3, module.power, true);
try_this_action("Waiting for signal", 10, module.signal);
try_this_action("Sending SMS", 3, module.sendSMS, "test");
try_this_action("Powering OFF", 1, module.power, false);

Если вам нужна дополнительная информация о функциях с переменными числами и использовании макросов stdarg.hГугл в сети.Начните отсюда https://en.cppreference.com/w/c/variadic.

Это может быть закодировано также как реализация макроса, как отличное предложение в ответе Джона Боллинджера, но в этом случае вы должны учитывать, что каждое использование макроса будет создавать весь код, чтов конечном итоге может быть даже лучше для скорости (избегая вызова функции), но может не подходить для систем с ограниченной памятью (встроенной) или для тех случаев, когда вам нужна ссылка на функцию try_this_action (не существует).

0 голосов
/ 06 февраля 2019

Трудность, с которой я столкнулся, состоит в том, что вызываемые функции имеют разный синтаксис (некоторые принимают параметры, другие нет ...).

Это действительно проблема.Наряду с этим у вас есть возможность варьировать фактические аргументы функции для одной и той же функции.

Есть ли более элегантный способ сделать это, кроме копирования / вставки фрагмента кода везде, где он мне нужен?

Я думаю, вы могли бы создать функцию с переменным числом аргументов, которая использует специальные знания функций для диспетчеризации, чтобы иметь дело с различными сигнатурами функций и фактическими аргументами.Я сомневаюсь, что посчитал бы результат более элегантным.

Я был бы склонен подойти к этой работе с помощью макроса, вместо этого:

// desc:     a descriptive string, evaluated once
// action:   an expression to (re)try until it evaluates to true in boolean context
// attempts: the maximum number of times the action will be evaluated, itself evaluated once
#define try_this_action(desc, action, attempts) do { \
    int _attempts = (attempts);                      \
    DEBUG_PRINT(desc);                               \
    while(_attempts && !(action)) {                  \
        _attempts -= 1;                              \
        DEBUG_PRINT(".");                            \
        delay(100);                                  \
    }                                                \
    if (_attempts) {                                 \
        DEBUG_PRINTLN(" - Success");                 \
    } else {                                         \
        DEBUG_PRINTLN(" - Failed.");                 \
        soft_reset();                                \
    }                                                \
    wdt_reset();                                     \
} while (0)

Использование будет таким же, как выописано:

try_this_action("Powering ON", module.power(true), 3);

и т. д. .. Хотя эффект такой, как если бы вы вставляли код для каждого действия в каждую точку, использование макроса, такого как этот, приведет к значительному кодулегче читать, и это не является лексическим повторением.Так, например, если вам когда-либо понадобится изменить шаги для выполнения действий, вы можете сделать это раз и навсегда, изменив макрос.

0 голосов
/ 06 февраля 2019

Функции разной арности могут быть абстрагированы общей подписью (подумайте о main).Вместо того, чтобы давать каждому свой уникальный аргумент, вы просто предоставляете им все:

  1. Количество аргументов.
  2. Вектор указателей на аргументы.

Так ваша операционная система обрабатывает все программы, которые она запускает.Ниже приведен очень простой пример, который вы можете проверить.

#include <stdio.h>
#include <stdlib.h>

/* Define total function count */
#define MAX_FUNC        2

/* Generic function signature */
typedef void (*func)(int, void **, const char *);

/* Function pointer array (NULL - initialized) */
func functions[MAX_FUNC];

/* Example function #1 */
void printName (int argc, void **argv, const char *desc) {
    fprintf(stdout, "Running: %s\n", desc);
    if (argc != 1 || argv == NULL) {
        fprintf(stderr, "Err in %s!\n", desc);
        return;
    }
    const char *name = (const char *)(argv[0]);
    fprintf(stdout, "Name: %s\n", name);
}

/* Example function #2 */
void printMax (int argc, void **argv, const char *desc) {
    fprintf(stdout, "Running: %s\n", desc);
    if (argc != 2 || argv == NULL) {
        fprintf(stderr, "Err in %s!\n", desc);
        return;
    }
    int *a = (int *)(argv[0]), *b = (int *)(argv[1]);
    fprintf(stdout, "Max: %d\n", (*a > *b) ? *a : *b);
}


int main (void) {
    functions[0] = printName;               // Set function #0
    functions[1] = printMax;                // Set function #1


    int f_arg_count[2] = {1, 2};            // Function 0 takes 1 argument, function 1 takes 2.
    const char *descs[2] = {"printName", "printMax"};
    const char *name = "Natasi";            // Args of function 0
    int a = 2, b = 3;                       // Args of function 1
    int *args[2] = {&a, &b};                // Args of function 1 in an array.

    void **f_args[2] = {(void **)(&name), 
                      (void **)(&args)};    // All function args.

    // Invoke all functions.    
    for (int i = 0; i < MAX_FUNC; i++) {
        func f = functions[i];
        const char *desc = descs[i];
        int n = f_arg_count[i];
        void **args = f_args[i];
        f(n, args, desc);
    }

    return EXIT_SUCCESS;
}
0 голосов
/ 06 февраля 2019

Вы должны сделать так, чтобы указатели на функции имели одинаковую подпись.Я бы использовал что-то вроде этого;

typedef int(*try_func)(void *arg);

И имел бы подпись try_this_action(...), аналогичную следующей;

void try_this_action(char * msg, int max_trys, try_func func, void *arg)

Затем вы бы реализовали свои действия, подобные этому;

int power(void *pv)
{
    int *p = pv;    
    int on_off = *p;

    static int try = 0;

    if (on_off && try++)
        return 1;
    return 0;
}

int signal(void *pv)
{
    static int try = 0;

    if (try++ > 6)
        return 1;
    return 0;
}

И назовите их вот так;

int main(int c, char *v[])
{
    int on_off = 1;

    try_this_action("Powering ON", 3, power, &on_off);
    try_this_action("Signaling", 10, signal, 0);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...