Once-only pseudo-generic header 在 C 中

Once-only pseudo-generic header in C

在对我在 上询问过的通用向量进行了一些工作之后,我想知道是否有任何方法可以检查库的每个实例化对于每种类型只执行一次。

这是当前 header 文件的样子:

#ifndef VECTOR_GENERIC_MACROS
#define VECTOR_GENERIC_MACROS

    #ifndef TOKENPASTE
    #define TOKENPASTE(a, b) a ## b
    #endif

    #define vector_t(T) TOKENPASTE(vector_t_, T)

    #define vector_at(T) TOKENPASTE(*vector_at_, T)

    #define vector_init(T) TOKENPASTE(vector_init_, T)
    #define vector_destroy(T) TOKENPASTE(vector_destroy_, T)
    #define vector_new(T) TOKENPASTE(vector_new_, T)
    #define vector_delete(T) TOKENPASTE(vector_delete_, T)

    #define vector_push_back(T) TOKENPASTE(vector_push_back_, T)
    #define vector_pop_back(T) TOKENPASTE(vector_pop_back_, T)
    #define vector_resize(T) TOKENPASTE(vector_resize_, T)
    #define vector_reserve(T) TOKENPASTE(vector_reserve_, T)

#endif

typedef struct {
    size_t size;
    size_t capacity;
    TYPE *data;
} vector_t(TYPE);

inline TYPE vector_at(TYPE)(vector_t(TYPE) *vector, size_t pos);

void vector_init(TYPE)(vector_t(TYPE) *vector, size_t size);
void vector_destroy(TYPE)(vector_t(TYPE) *vector);
inline TYPE *vector_new(TYPE)(size_t size);
inline void vector_delete(TYPE)(vector_t(TYPE) *vector);

void vector_push_back(TYPE)(vector_t(TYPE) *vector, TYPE value);
inline TYPE vector_pop_back(TYPE)(vector_t(TYPE) *vector);
inline void vector_resize(TYPE)(vector_t(TYPE) *vector, size_t size);
void vector_reserve(TYPE)(vector_t(TYPE) *vector, size_t size);

然后可以将 header 与源定义一起包括在内:

#include <stdio.h>

#define TYPE int
#include "vector.h"
#include "vector.def"
#undef TYPE

int main()
{
    vector_t(int) myVectorInt;
    vector_init(int)(&myVectorInt, 0);

    for (int i = 0; i < 10; ++i)
        vector_push_back(int)(&myVectorInt, i);

    for (int i = 0; i < myVectorInt.size; ++i)
        printf("%d ", ++vector_at(int)(&myVectorInt, i));

    vector_destroy(int)(&myVectorInt);
    return 0;
}

我想确保每个 TYPE 只包含最后一个 endif 下面的内容。

显然,#ifdef VECTOR_INSTANCE(TYPE) 不起作用,所以我真的没主意了...

建议:

从 vector.h 文件中删除原型。

将原型放在 vector.def 文件的顶部。

从 vector.h 文件中删除 typedef 结构

将 typedef 结构放在 vector.def 文件中的原型之前。

那么 vector.h 文件的多个 #include 语句将不会产生不良影响。

然后在每个要使用这些矢量类型的源文件中使用以下内容:

#include<vector.h>

#define TYPE int
#include<vector.def>
#undef TYPE

#define TYPE char
#include<vector.def>
#undef TYPE
... etc


BTW:
There is no library involved, so I'm a bit confused by the reference 
to 'library' in the question

It may be worthwhile to also prefix the 'static' modifier 
to each of the function definitions so the definitions are 
not visible across source files

It may be worthwhile to use parens around the parameters to TOKENPASTE
so modifiers like 'static' and.or 'const' 
can be prefixed to the function names.

虽然是个问题,但是,前段时间我向您similar question提问时,我也对此事很感兴趣。

我的结论是,如果您打算使用许多不同类型的向量(或者,使用更准确的命名,动态数组),那么拥有所有这些函数是一种浪费 vector_##TYPE##_reserve()vector_##type##_resize()等...多次。

相反,将这些函数在单独的 .c 文件中只定义一次会更加高效和干净,使用您的类型的大小作为额外参数。这些函数的原型在一个单独的 .h 文件中。然后,同一个 .h 文件将提供为您自己的类型生成函数包装器的宏,这样您就不会看到它使用大小作为额外参数。

例如,您的 vector.h header 将包含以下内容:

/* Declare functions operating on a generic vector type */
void vector_generic_resize(void *vector, size_t size, size_t data_size);
void vector_generic_push_back(void *vector, void *value, size_t data_size);
void *vector_generic_pop_back(void *vector, size_t data_size);
void vector_generic_init(void *vector, size_t size, size_t data_size);
void vector_generic_destroy(void *vector) ; // I don't think data_size is needed here

/* Taken from the example in the question */
#define VECTOR_DEFINITION(type)\
typedef struct {\
    size_t size;\
    size_t capacity;\
    type *data;\
} vector_ ## type ## _t;\

/* Declare wrapper macros to make the above functions usable */
/* First the easy ones */
#define vector_resize(vector, size) vector_generic_resize(vector, size, sizeof(vector.data[0]))
#define vector_init(vector, size) vector_generic_init(vector, size, sizeof(vector.data[0]))
/* Type has to be given as an argument for the cast operator */
#define vector_pop_back(vector, type) (*(type*)(vector_generic_pop_back(vector, sizeof(vector.data[0]))))

/* This one is tricky, if 'value' is a constant, it's address cannot be taken.
I don't know if any better workarround is possible. */
#define vector_push_const(vector, type, value)                    \
{                                                                 \
    type temp = value;                                         \
    vector_generic_push_back(vector, &temp, sizeof(vector.data[0]));\
}

/* Equivalent macro, but for pushing variables instead of constants */
#define vector_push_var(vector, value) vector_generic_push_back(vector, &value, sizeof(vector.data[0]))

/* Super-macro rediriging to constant or variable version of push_back depending on the context */
#define GET_MACRO(_1,_2,_3,NAME,...) NAME
#define vector_push_back(...) GET_MACRO(__VA_ARGS__, vector_push_const, vector_push_var)(__VA_ARGS__)

/* This macro isn't really needed, but just for homogenity */
#define vector_descroy(vector) vector_generic_destroy(vector)

然后可以按照您在链接的示例中所说的那样使用函数,但 vector_generic_push_back 是一个重要的例外,不幸的是,每次都必须将类型指定为额外的宏参数。

所以有了这个解决方案

  • 你只需要在 .c 文件中做 VECTOR_DEFINITION(),避免了用相同类型声明它两次的风险
  • 矢量库在二进制文件中只存在一次
  • 除了 pop back 宏和 push literal 宏之外,宏可以优雅地使用而无需在名称中使用类型。
  • 如果这是一个问题,您可以让推送文字始终使用 long long,它会起作用,但可能会降低效率。
  • 类似地,您可以使 pop_back() 宏和 vector_generic_pop_back() 函数不像它们在 C++ 语言中所做的那样 return,所以如果您同时使用这两个技巧您永远不需要在宏中明确使用类型名称。

作为参考,您在问题中链接的示例中发布的主要功能必须像这样进行调整:

    #include <stdio.h>
    #include <stdlib.h>
    #include "vector.h"

    typedef unsigned int uint;
    typedef char* str;

    VECTOR_DEFINITION(uint)
    VECTOR_DEFINITION(str)

    int main()
    {
        vector_uint_t vector;
        vector_init(&vector, 10);

        for (unsigned int i = 0; i < vector.size; ++i)
            vector.data[i] = i;

        for (unsigned int i = 0; i < 10; ++i)
            vector_push_back(&vector, i);

        /* When pushing back a constant, we *have* to specity the type */
        /* It is OK to use C keywords as they are supressed by the preprocessor */
        vector_push_back(&vector, unsigned int, 12);

        for (unsigned int i = 0; i < vector.size; ++i)
            printf("%d ", vector.data[i]);
        printf("\n");

        vector_destroy(&vector);

        vector_str_t sentence;
        vector_init(&sentence, 0);

        vector_push_back(&sentence, "Hello");
        vector_push_back(&sentence, str, "World!");  /* Also possible, less efficient */
        vector_push_back(&sentence, "How");
        vector_push_back(&sentence, "are");
        vector_push_back(&sentence, "you?");

        for (unsigned int i = 0; i < sentence.size; ++i)
            printf("%s ", sentence.data[i]);
        printf("\n");

        vector_destroy(&sentence);

        return 0;
    }