Как элегантно реализовать серию функций в разных версиях, используя чистый C? - PullRequest
9 голосов
/ 25 августа 2011

Я хочу написать несколько функций, которые отличаются только типами аргументов. Я знаю, что C ++ имеет template, чтобы хорошо справиться с этой проблемой (хотя пока не очень хорошо, лишь немногие компиляторы поддерживают ключевое слово export, и это ключевое слово запрашивается для эффективности). Для простого примера я хочу:

template <typename T>
T add(T a, T b){
    return a+b;
}

Однако в чистом C (иногда мне приходится выбирать чистый C, так как на некоторых платформах нет компилятора C ++), для разных версий должны быть разные имена функций, например

double addDouble(double a, double b){
    return a+b;
}

int addInt(int a, int b){
    return a+b;
}

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

Пока я пробовал некоторые решения, как показано ниже, но я думаю, что они далеки от хороших. Мне нужны ваши предложения, спасибо!

Решение 1:

#define mytype int
mytype addInt(mytype a, mytype b){
    return a+b;
}
#undef mytype

#define mytype float
mytype addFloat(mytype a, mytype b){
    return a+b;
}
#undef mytype

Недостаток решения 1: дублированного содержимого слишком много, и если я хочу изменить функцию, я должен изменить все версии.

Решение 2:

func.h

#ifndef FUNC_H
#define FUNC_H

#define add(a, b, typename) functionAdd##typename(a,b)

/* function declarations */
#define declared(typename) \
typename functionAdd##typename(typename, typename)

declared(int);
declared(float);

#endif

func.c

#include "func.h"

/* function code */
#define functionAdd(a, b, typename) \
typename functionAdd##typename(typename a, typename b){ \
    return a+b; \
}

/* function bodies (definitions) */
functionAdd(a, b, int)
functionAdd(a, b, float)

main.c

#include <stdio.h>
#include "func.h"

int main()
{
    int x1 = add(1, 2, int);
    float x2 = add(3.0, 4.0, float);
    printf("%d %f\n", x1, x2);  
    return 0;
}

Недостаток решения 2: Поскольку функция написана на define, ее сложно отладить. Кроме того, запись \ раздражает. Хотя новую версию удобно добавлять, просто вставив declared(double) в func.h и functionAdd(a, b, double) в func.c, вы достигнете этой цели.

Ответы [ 5 ]

10 голосов
/ 25 августа 2011

Во многих (если не в большинстве) случаях лучшим способом для имитации шаблонов C ++ в C будет Решение 3 : параметризованный заголовочный файл и параметризованный файл реализации.В вашем случае это будет работать следующим образом:

  1. Создать файл мета-заголовка, который мы назовем add.dec, который выглядит следующим образом

    TYPE_ CONCAT(add, SUFFIX_)(TYPE_ a, TYPE_ b);
    TYPE_ CONCAT(sub, SUFFIX_)(TYPE_ a, TYPE_ b);
    
  2. Создайте файл мета-реализации, который мы назовем add.def, который выглядит следующим образом

    TYPE_ CONCAT(add, SUFFIX_)(TYPE_ a, TYPE_ b){
      return a + b;
    }
    
    TYPE_ CONCAT(sub, SUFFIX_)(TYPE_ a, TYPE_ b){
      return a - b;
    }
    

Эти два файла параметризованы двумя макросами: TYPE_ и SUFFIX_, в то время как CONCAT - это традиционная реализация конкатенации макросов

#define CONCAT_(a, b) a##b
#define CONCAT(a, b) CONCAT_(a, b)

Теперь представьте, что вы хотите создать экземпляр функции-шаблона для типов int и double.В "реальном" заголовочном файле add.h вы просто делаете

#define TYPE_ int
#define SUFFIX_ Int
#include "add.dec"
#undef TYPE_
#undef SUFFIX_

#define TYPE_ double
#define SUFFIX_ Double
#include "add.dec"
#undef TYPE_
#undef SUFFIX_

, а в "реальном" файле реализации add.c вы делаете

#define TYPE_ int
#define SUFFIX_ Int
#include "add.def"
#undef TYPE_
#undef SUFFIX_

#define TYPE_ double
#define SUFFIX_ Double
#include "add.def"
#undef TYPE_
#undef SUFFIX_

Вот и все.Делая это, вы создаете (объявляете и определяете) addInt, addDouble, subInt и subDouble.

Конечно, вы можете параметризовать объявления гораздо больше.Вы можете добавить параметр DECLSPEC_, чтобы при необходимости можно было объявить ваши солнечные лучи как static.Вы можете указать различные типы параметров и возвращаемых значений (скажем, ARG_TYPE_ и RET_TYPE_).Вы можете параметризовать множество других вещей.По сути, нет предела тому, что вы можете параметризовать.С некоторыми довольно простыми методами макросов вы можете даже параметризовать количество параметров, ожидаемых вашими функциями.

Это на самом деле похоже на ваше решение 1 и решение 2 вместе взятые.Это в основном берет лучшее из обоих ваших подходов.И я бы сказал, что это наиболее точная попытка смоделировать поведение экземпляра шаблона C ++.

Обратите внимание, что тело каждой функции явно вводится только один раз (в отличие от нескольких явных копий в вашем решении 1).Текущие тела функций также легко редактируются, поскольку вам не нужно беспокоиться об этих надоедливых \ в конце каждой строки (как в случае с вашим решением 2).

У этого подхода есть еще одно интересное преимущество: код в add.def останется «отлаживаемым», т. Е. Обычный интерактивный отладчик, как правило, сможет войти в эти реализации (что невозможно в вашем решении 2).

2 голосов
/ 25 августа 2011

Я бы также предложил Решение 4 : написать инструмент для генерации кода.

Плюсы:

  • результат - чистый отлаживаемый код;
  • неограниченная конфигурируемость для ваших нужд (если у вас есть время, конечно);
  • долгосрочные инвестиции в набор инструментов разработчика.

Минусы:

  • занимает некоторое время, особенно при запуске не всегда подходит для однократной записи;
  • немного усложняет процесс сборки.
1 голос
/ 11 апреля 2015

Вы можете использовать союз:

#include <stdio.h>
#include <stdarg.h>

typedef enum {Int, Double} NumberType;

typedef struct {
  NumberType type;
  union {
    int i;
    double d;
  };
} Number;

Number addNumber(Number a, Number b) {
  Number ret;
  Number *numbers[] = {&a, &b};
  if (a.type == Int && b.type == Int ){
    ret.type = Int;
    ret.i = a.i + b.i;
  }
  else {
    ret.type = Double;
    char i;
    for (i = 0; i < 2 && numbers[i]->type == Int; i++) {
      numbers[0]->d = (double) numbers[i]->i;
    }
    ret.d = a.d + b.d;
  }
  return ret;
}

Number newNumber(NumberType type, ...) {
  va_start(ap, type);
  Number num;
  num.type = type;
  switch (type) {
    case Int: {
      num.i = va_arg(ap, int);
      break;
    }
    case Double: {
      num.d = va_arg(ap, double);
      break;
    }
    default: { /* error */
      num.type = -1;
    }
  }
  va_end(ap);
  return num;
}


int main(void) {
  Number a = newNumber(Int, 1);
  Number b = newNumber(Double, 3.0);
  Number ret = addNumber(a, b);
  switch (ret.type) {
    case Int: {
      printf("%d\n", ret.i);
    }
    case Double: {
      printf("%f\n", ret.d);
    }
  }
  return 0;
}
1 голос
/ 25 августа 2011

Если вы считаете, что использование препроцессора C неудобно и его трудно отлаживать, как насчет написания сценария на более удобном языке для создания файла .c, который вы можете #include? Большинство современных языков сценариев поставляются с каким-то механизмом шаблонов, но поскольку ваши требования достаточно просты, он не должен быть более сложным, чем этот;

#/bin/sh
for t in int double char mytype; do
    cat <<____HERE
    $t add_$t ($t a, $t b) {
        return (a + b);
    }
____HERE
done >generated.c

Полученный файл будет простым Cane, который должен быть достаточно простым для отладки и изменения.

0 голосов
/ 25 августа 2011

Я не думаю, что вы можете сделать намного лучше, чем ваш раствор 2 в чистом C.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...