如何使用纯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 :参数化头文件和参数化实现文件。 在你的情况下,它将如下工作
-
创建一个元头文件,我们将其命名为
add.dec
,如下所示TYPE_ CONCAT(add, SUFFIX_)(TYPE_ a, TYPE_ b); TYPE_ CONCAT(sub, SUFFIX_)(TYPE_ a, TYPE_ b);
-
创建一个元实现文件,我们将其命名为
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中是不可能的)。
我还建议解决方案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做得更好。