简体   繁体   English

C语言中通用数据结构实现的最佳实践

[英]Best practice for generic data structure implementation in C

In my adventures implementing generic data structures in C, I've come across a dilemma. 在我用C实现通用数据结构的冒险中,我遇到了一个难题。 For example, in the following code: 例如,在以下代码中:

void add_something(avl_tree_t * my_tree) {
        int new_element = 123;

        avl_insert(my_tree, (void*)&new_element);
}

int main() {
        avl_tree_t * my_tree = avl_create();

        add_something(my_tree);

        // do stuff

        avl_print(my_tree, function_that_prints_ints);

        exit(0);
}

In which avl_insert is defined as 其中avl_insert定义为

void avl_insert(avl_tree_t * tree, void * data) {
        avl_node_t * new_node = malloc(sizeof(struct avl_node));

        new_node->data = data;
        // do tree balancing stuff
}

In order for my generic insertion function to work, I have to pass it a void * item to store. 为了使我的通用插入函数起作用,我必须将它传递给void *项进行存储。 However, in order for that to work, in this case I need to pass in the address of the new int item I'm adding so that I can then dereference it to a void * . 但是,为了使其正常工作,在这种情况下,我需要传递要添加的新int项的地址,以便可以将其取消引用为void * If I am not mistaken, when we're back in the main function, the memory address in which I stored my new element will be compromised. 如果我没记错的话,当我们回到main函数中时,存储新元素的内存地址将受到损害。

One way I looked into to solve this issue is to pass in the size of the things I am storing in the tree as a parameter for avl_create , and then allocating memory for a copy of each element I insert. 我研究解决此问题的一种方法是将存储在树中的内容的大小作为avl_create的参数avl_create ,然后为插入的每个元素的副本分配内存。 This works because you don't need the original address or value for whatever you added. 之所以有效,是因为您不需要添加任何内容的原始地址或值。

Another thing that works is only using the data structure in the span of a single function, which is obviously not viable. 起作用的另一件事是仅在单个函数的范围内使用数据结构,这显然不可行。

My question is this: what is the best way to go about storing statically allocated data in a generic data structure, be it basic C types or user made structures? 我的问题是:在基本数据类型或用户创建的结构中,将静态分配的数据存储在通用数据结构中的最佳方法是什么?

Thank you in advance. 先感谢您。

To store pointers to data with automatic storage duration, yes, you would have to know the size of the elements in the container and allocate and copy the pointed-to data. 要以自动存储持续时间存储指向数据的指针,是的,您将必须知道容器中元素的大小,并分配和复制指向的数据。

The simplest way is to just allocate and copy in all cases, optionally using a user-specified clone() or create() function to make deep copies, if necessary. 最简单的方法是在所有情况下都进行分配和复制,必要时可以选择使用用户指定的clone()create()函数进行深层复制。 This also entails the use of a user-specified destroy() function to dispose of the copies properly (again, if necessary). 这也需要使用用户指定的destroy()函数来正确处理副本(再次,如有必要)。

To be able to avoid the allocation, then you have to have some kind of state variable that lets you know if the container should allocate, or just copy the pointer value itself. 为了避免分配,您必须具有某种状态变量,让您知道容器是否应该分配,或者只是复制指针值本身。

Note that this should apply to the container object, not to the individual nodes or elements. 请注意,这应适用于容器对象,而不适用于各个节点或元素。 If a container stores data in one way or the other, it should store all data that way. 如果容器以一种或另一种方式存储数据,则应以该方式存储所有数据。 See Principle of Least Astonishment . 参见最小惊讶原则

This is the more complex approach, since you have to be sure to use the correct process for adding and deleting elements based on the state variable. 这是更复杂的方法,因为您必须确保使用正确的过程来基于状态变量添加和删除元素。 It's ususally much simpler to just make sure you never pass in a pointer to a value with automatic storage duration. 通常,只需确保您永远不会传递带有自动存储持续时间的值的指针,通常会容易得多。

Use a mix-in style; 使用混合样式; eg do not make data part of the node but the node part of the data: 例如,不使数据成为节点的一部分,而是使节点成为数据的一部分:

struct avl_node {
    struct avl_node *parent;
    struct avl_node *left;
    struct avl_node *right;
};

struct person {
    char const *name;
    struct avl_node node;
};

struct animal {
    struct avl_node node;
    int dangerousness;
};

Constructors for animal are like animal构造函数就像

struct animal *animal_create(double d)
{
    struct animal *animal = malloc(sizeof *animal);

    *animal = (struct animal) {
        .node = AVL_NODE_INIT(),
        .dangerousness = d,
    };

    return animal;
}

The generic AVL tree operations could look like 通用AVL树操作可能看起来像

void avl_tree_insert(struct avl_node **root, struct avl_node *node, 
                     int (*cmp)(struct avl_node const *a, struct avl_node const *b))
{
    /* .... */
}

and a cmp function for animal like 以及对animalcmp功能

int animal_cmp(struct avl_node const *a_, struct avl_node const *b_)
{
     struct animal const *a = container_of(a_, struct animal, node);
     struct animal const *b = container_of(b_, struct animal, node);

     return a->dangerousness - b->dangerousness;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM