Есть ли способ избежать дублирования кода в нескольких похожих функциях? - PullRequest
0 голосов
/ 14 февраля 2020

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

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

Следующие работы демонстрируют эту концепцию:

// func.inc

// The absence of an include guard is intentional, as each time this file gets included
// the output will be different

void FUNC(int x)
{
    /* SNIP - lots and lots of code that is duplicated between 
       variant A and B (and more) of the function
    for ( ... 4096 ) 
    {
        for( lots more nested loops)
        {
    */
    // IMPORTANT - I do not want to call functions here as it is
    // in a tight loop withconsecutive memory accesses of 
    // different sizes of strided sparse arrays
#ifdef A
    printf("A %d\n", x);
#endif
#ifdef B
    printf("B %d\n", x);
#endif
    /*
        }
    }
    */
// main.c

#include <stdio.h>

#define FUNC func_A  
#define A
#include "func.inc"
#undef A
#undef FUNC

#define FUNC func_B  
#define B
#include "func.inc"
#undef B
#undef FUNC

#define FUNC func_AB  
#define A
#define B
#include "func.inc"

int main()
{
    func_A(10);
    func_B(20);
    func_AB(30);

    printf("Done\n");

    return 0;
}

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

Есть ли решение, которое любой может предложить, не дублируя несколько слегка отличающихся версий одной и той же функции?

Ответы [ 2 ]

4 голосов
/ 14 февраля 2020

Не совсем понятно, что здесь псевдокод и реальный код, но в целом вы не должны использовать #define + #undef + #include для целей генерации другого кода. (Вы можете сделать это с помощью «X macros», хотя в качестве крайней меры. Не идеальное решение, но лучше, чем это.)

Решение «ВАЖНО - я не хочу звонить функции здесь ", чтобы вызвать функции.

Встраивание функций существовало уже около 30 лет, и 20 лет go C получили явную поддержку языка. И в настоящее время компиляторы намного лучше, чем программисты, чтобы определить, что встраивать. Я приведу пример с явным inline только для демонстрации того, что вызывающие функции не влияют на производительность, если все сделано правильно.

С традиционным C вы бы сделали что-то вроде этого :

#include <stdio.h>

static inline void SNIP (void)
{
  puts(__func__);
}

static inline void A_stuff (int val)
{
  printf("%s %d\n", __func__, val);
}

static inline void B_stuff (int val)
{
  printf("%s %d\n", __func__, val);
}

typedef enum { A=1, B=2 } AB_t;

void func(AB_t ab, int val)
{
  SNIP();

  if(ab & A)
    A_stuff(val);
  if(ab & B)
    B_stuff(val);
}

int main()
{
  func(A, 10);
  func(B, 20);
  func(A|B, 30);

  printf("Done\n");
  return 0;
}

Это нормальное решение. Единственными функциями, которые фактически вызываются в сгенерированном машинном коде, являются func и функции печати. ​​

В качестве альтернативы, вы могли бы также выполнить генерацию кода с помощью «X-макросов» - они существуют исключительно для того, чтобы избежать повторение кода, за счет читабельности. Не очень рекомендую это здесь, но я приведу пример для полноты:

#include <stdio.h>

#define FUNC_LIST \
  X(A,  10)       \
  X(B,  20)       \
  X(AB, 30)       \


static inline void SNIP (void)
{
  puts(__func__);
}

static inline void A_stuff (int val)
{
  printf("%s %d\n", __func__, val);
}

static inline void B_stuff (int val)
{
  printf("%s %d\n", __func__, val);
}

static inline void AB_stuff (int val)
{
  A_stuff(val);
  B_stuff(val);
}

#define X(opt, val) void func_##opt (int x) { SNIP(); opt##_stuff(x); }
  FUNC_LIST
#undef X


int main()
{
  #define X(opt, val) func_##opt(val),
    FUNC_LIST
  #undef X

  printf("Done\n");
  return 0;
}

Это довольно нечитабельно, как и оригинальный код, за исключением того, что "X-макросы" являются чем-то де-факто стандартом для icky макрос трюки, чтобы избежать повторения кода.

Это создает несколько функций, как шаблон C ++, поэтому он также не идеален по этой причине.

0 голосов
/ 14 февраля 2020

Редактировать: Вопрос был изначально помечен C ++, отсюда и ответ. Позже тег был удален без достаточного обоснования (OP указал, что он использует компилятор C ++, что является обязательным требованием для использования тега C ++ для кода, который является допустимым C ++ и действительным C) и мой комментарий, указывающий на то, что (вместе с ОП о компиляторе C ++) под вопросом был удален людьми, которые предположительно удалили тег. Тем не менее я оставлю ответ без ответа.


Создайте шаблон!


// func.hpp

#ifndef FUNCITON_HPP
#define FUNCITON_HPP

enum Specifier : int {
        A = 1 << 0,
        B = 1 << 1,
};


#include <cstdio>

template <auto sp>
void foo(int x)
{
        /* SNIP - lots and lots of code that is duplicated between
           variant A and B (and more) of the function
        for ( ... 4096 )
        {
            for( lots more nested loops)
            {
        */
        // IMPORTANT - I do not want to call functions here as it is
        // in a tight loop withconsecutive memory accesses of
        // different sizes of strided sparse arrays
        if constexpr (static_cast<bool>(sp & Specifier::A)) {
                std::printf("A %d\n", x);
        }
        if constexpr (static_cast<bool>(sp & Specifier::B)) {
                std::printf("B %d\n", x);
        }
}

#endif //!FUNCITON_HPP

Тогда


// func.cpp
#include "func.hpp"

auto constexpr func_a = foo<Specifier::A>; // Could also use a #define 
auto constexpr func_b = foo<Specifier::B>;
auto constexpr func_ab = foo<Specifier::A | Specifier::B>;


int main()
{
        func_a(1);
        func_b(1);
        func_ab(1);
}
...