写一个带有void *的函数是有优势的,而不是多个函数,每个函数都采用不同的类型?

时间:2017-03-08 06:57:30

标签: c

我最近试图制作一个动态数组库+一个矩阵库,让我的脑袋更多地包裹着C,特别是指针。

然而,最近我发现我以不同于其他库的方式做事,例如gsl。我一直试图创建一个单独的函数/结构来处理c +用户定义的类型中的每一个类型,但是当我查看gsl并且特别是它的矩阵部分时,它们以一种非常不同的方式定义它。 gsl库具有多种结构,用于不同的数据类型(matrix_intmatrix_floatmatrix_double等)以及一组仅适用于该结构的函数({{1等等)。我的问题是,为每种数据类型设置函数/结构是否有优势?为什么不使用void指针代替只有一组结构/函数?

4 个答案:

答案 0 :(得分:2)

解决问题,是的,为每种类型实现一个功能有几个优点,在某些情况下是强制性的。一些针对高性能的库 使用非常具体的指令,以基于变量类型的有效方式处理数据的获取/处理/写入。

private <T> ObservableTransformer<T, T> debounceImmediate() { return observable -> observable.publish(p -> Observable.merge(p.throttleFirst(1, TimeUnit.SECONDS), p.debounce(1, TimeUnit.SECONDS)).distinctUntilChanged()); } @Test public void testDebounceImmediate() { Observable.just(0, 100, 200, 1500, 1600, 1800, 2000, 10000) .flatMap(v -> Observable.timer(v, TimeUnit.MILLISECONDS).map(w -> v)) .doOnNext(v -> System.out.println(LocalDateTime.now() + " T=" + v)) .compose(debounceImmediate()) .blockingSubscribe(v -> System.out.println(LocalDateTime.now() + " Debounced: " + v)); } float就是一个明显的例子,即使它们具有相同的大小(对于32位处理器), 表示完全不同,操作由不同的操作单元处理,int intALU FPU处理。

此外,对于C11编译器,您可以使用float,但如果您使用C99,则无法知道变量的类型。 _Generic() _Generic()在编译时工作,因此您最终会得到每种类型的函数。

答案 1 :(得分:2)

您可以轻松正确地编写函数 matrixop(void * arg,int type_of_arg等),然后根据 type_of_arg 强制转换 arg STRONG>。当然,正如已经说过的,为了使功能正常工作,可能必须对不同的 type_of_arg 进行不同的操作。但是用户不必知道这一点,并且只会看到一个函数。而且,既然你说,&#34;让我的头脑更多地包裹着C,特别是指针&#34;,我绝对建议你试试这种方式。您的特定矩阵示例可能不是最有效的情况,其中void指针是最有用的,但它对于练习来说绝对是好的和花花公子。

答案 2 :(得分:1)

  

我的问题是,为每种数据类型设置函数/结构是否有优势?

是。它增加了编译时类型检查。

用于为不同类型实现相同操作的代码确实不同,并且不一定只是使用的元素类型。 (对于矩阵运算,例如,相同大小的整数和浮点类型之间的最佳缓存策略可能不同;特别是如果硬件支持向量化。)这意味着每个元素类型都需要各自操作的版本。

可以使用一些模板技术来生成仅按类型不同的操作的元素类型特定版本,但通常最终结果比仅单独维护略有不同的实现更复杂(因此更难维护)。

很有可能使用预处理器和GCC扩展(__typeof__)或C11 _Generic()添加一个额外的层 - 无需修改,只需在GSL之后包含一个额外的头文件 - 为每个矩阵运算提供一个“函数”,它根据参数的类型选择在编译时调用的函数。

  

为什么不使用void指针代替只有一组结构/函数?

因为不仅丢失了编译时类型检查 - 用户可以提供一个文字字符串,并且无论启用什么警告,编译器都不会警告它,但它也会添加运行时开销。

不必选择在编译时调用的正确函数(实现),而是必须检查数据类型字段并在运行时调用正确的函数。例如,通用矩阵乘法函数可能看起来像

status_code_type matrix_multiply(void *dest, void *left, void *right)
{
    const element_type  tleft = ((struct generic_matrix_type *)left)->type;
    const element_type  tright = ((struct generic_matrix_type *)right)->type;

    if (tleft != tright)
        return ERROR_TYPES_MISMATCH;

    switch (tleft) {
    case ELEMENT_TYPE_INT:
        return matrix_mul_int_int(dest, left, right);
    case ELEMENT_TYPE_FLOAT:
        return matrix_mul_float_float(dest, left, right);
    case ELEMENT_TYPE_DOUBLE:
        return matrix_mul_double_double(dest, left, right);
    case ELEMENT_TYPE_COMPLEX_FLOAT:
        return matrix_mul_cfloat_cfloat(dest, left, right);
    case ELEMENT_TYPE_COMPLEX_DOUBLE:
        return matrix_mul_cdouble_cdouble(dest, left, right);
    default:
        return ERROR_UNSUPPORTED_TYPE;
    }
}

以上所有代码都是纯粹的开销,其唯一目的是使程序员“稍微容易”。例如,GSL开发人员认为没有必要或有用。

相当多的C代码 - 包括大多数C库的FILE实现 - 确实利用了相关的方法,但是:数据结构本身包含数据类型支持的每个操作的函数指针,在对象中面向时尚。

例如,您可以

struct matrix {
    long     rows;
    long     cols;
    long     rowstep; /* Number of bytes to next row */
    long     colstep; /* Number of bytes to next element */
    size_t   size;    /* Size of each element */
    int      type;    /* Type of each element */
    char    *data;    /* Logically void*, but allows pointer arithmetic */
    int    (*supports)(int, int);
    int    (*get)(struct matrix *, long, long, int, void *);
    int    (*set)(struct matrix *, long, long, int, const void *);
    int    (*mul)(struct matrix *, long, long, int, const void *);
    int    (*div)(struct matrix *, long, long, int, const void *);
    int    (*add)(struct matrix *, long, long, int, const void *);
    int    (*sub)(struct matrix *, long, long, int, const void *);
};

其中

int supports(int source_type, int target_type);

用于查明其他回调是否支持两种类型之间的必要操作,以及其余的成员函数,

int get(struct matrix *m, long row, long col, int to_type, void *to);
int set(struct matrix *m, long row, long col, int from_type, void *from);
int mul(struct matrix *m, long row, long col, int by_type, void *by);
int div(struct matrix *m, long row, long col, int by_type, void *by);
int add(struct matrix *m, long row, long col, int by_type, void *by);
int sub(struct matrix *m, long row, long col, int by_type, void *by);

对给定矩阵的单个元素进行操作。注意我们如何将引用传递给矩阵本身;如果我们打电话给some->get(...)函数指针指向的函数get不会自动获取指向它的结构的指针。

还要注意从矩阵(get)读取的值,或者在操作中使用的值是如何通过指针提供的;并且单独提供指针指定的数据类型。如果你想要一个函数来初始化一个矩阵以使其工作,而不需要用户自己为它们的自定义类型实现每个矩阵运算函数,那么这是必需的。

因为对元素的访问涉及间接调用,所以函数指针的开销非常大 - 特别是如果你考虑单元素操作实际上有多简单和快速。 (例如,一个操作的5个时钟周期间接调用开销本身只需要10个时钟周期,增加了50%的开销!)

答案 3 :(得分:0)

这取决于你的功能。如果他们根本没有实际使用数据,那么SELECT * FROM ( SELECT *, RANK() OVER (ORDER BY attr_B) AS rnk FROM Demo) AS t WHERE t.rnk = 1 可能是正确的,而如果他们确实需要知道有关数据的任何信息,那么指定类型是正确的方法。

例如,您的动态数组库可能不需要单独的函数来向数组添加,删除,排序(等)void*int数据项。在这种情况下,函数不需要知道有关所存储对象类型的任何信息,只需知道它的位置;在这种情况下传递float是正确的。

另一方面,矩阵库可能需要针对不同数据类型的不同原型,因为void*int(等)数据使用不同的指令来操纵它们。

相关问题