如何使用纯C优雅地实现不同类型版本的一系列function?

我想写几个只在参数类型上有所不同的函数。 我知道C ++有很好的template可以很好地处理这个问题(虽然不是很好,但很少有编译器支持export关键字,并且查询此关键字的效率)。 举个简单的例子,我想:

 template  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语句,而且会有更多的版本。 那么,我的问题是,如何优雅地实现不同类型版本的一系列function?

到目前为止,我已经尝试了一些解决方案,但我认为它们远非好。 我需要你的建议,谢谢!

解决方案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的缺点:重复内容太多,如果我想修改function,我必须修改所有版本。

解决方案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  #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即可实现此目的。

在许多(如果不是大多数)情况下,在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) 

现在,假设您想要为intdouble类型实例化“模板”函数。 在一个“真正的”头文件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_ 

而已。 通过这样做,您实例化(声明和定义) addIntaddDoublesubIntsubDouble

当然,您可以更多地参数化声明。 如有必要,您可以添加DECLSPEC_参数以将阳光声明为static 。 您可以为参数和返回值指定不同的类型(例如, ARG_TYPE_RET_TYPE_ )。 你可以参数化很多其他的东西。 基本上,您可以参数化的内容没有限制。 使用一些相当简单的宏技术,您甚至可以参数化函数所期望的参数数量。

这实际上类似于您的解决方案1和解决方案2的组合。 这基本上可以从您的两种方法中获得最佳效果。 我会说这是模仿C ++模板实例化行为的最忠实的尝试。

请注意,每个函数的主体只显式键入一次(与解决方案1中的多个显式副本相对)。 实际的函数体也很容易编辑,因为不需要担心每行末尾的那些讨厌的问题(如解决方案2中的情况)。

这种方法有另一个有趣的好处: add.def的代码将保持“可调试”,即普通的交互式调试器通常能够进入这些实现(这在解决方案2中是不可能的)。

我还建议解决方案4 :编写代码生成工具。

优点:

  • 结果是一个干净的可调试代码;
  • 根据您的需求无限制地配置(如果您有足够的时间);
  • 对开发工具集的长期投资。

缺点:

  • 需要一些时间,尤其是 在开始时,并不总是可写的一次写入代码;
  • 使构建过程复杂化一点。

如果您认为使用C预处理器很难调试,那么如何使用更方便的语言编写脚本来生成.c文件,您可以#include ? 大多数现代脚本语言都带有某种模板引擎,但由于您的要求很简单,因此它不必比这更复杂;

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

生成的文件将是简单易懂的C,调试和更改应该相当简单。

你可以使用union:

 #include  #include  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 = ai + bi; } 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 = ad + bd; } 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; } 

我不认为你能比纯C中的解决方案2做得更好。