【问题标题】:Best practice for generic data structure implementation in CC中通用数据结构实现的最佳实践
【发布时间】:2019-04-07 15:45:11
【问题描述】:

在我用 C 实现通用数据结构的冒险中,我遇到了一个难题。例如,在以下代码中:

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);
}

其中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
}

为了让我的通用插入函数能够正常工作,我必须将void * 项目传递给它以进行存储。但是,为了使其正常工作,在这种情况下,我需要传入我正在添加的新 int 项目的地址,以便我可以将其取消引用到 void *。如果我没记错的话,当我们回到 main 函数时,我存储新元素的内存地址将被泄露。

我研究解决此问题的一种方法是将存储在树中的东西的大小作为avl_create 的参数传递,然后为我插入的每个元素的副本分配内存。这是有效的,因为您添加的任何内容都不需要原始地址或值。

另一件可行的事情是只在单个函数的范围内使用数据结构,这显然是不可行的。

我的问题是:将静态分配的数据存储在通用数据结构中的最佳方法是什么,无论是基本的 C 类型还是用户制作的结构?

提前谢谢你。

【问题讨论】:

  • 如果对象具有静态存储时长,那么它不会去任何地方,您不必担心复制它。如果它有自动存储期限,那么,是的,你必须分配和复制。
  • 那么,我应该面对这两种情况的解决方案是为每个用例实现两种不同的数据结构吗?谈论开销。谢谢!
  • 不,您只需要使用适用于所有情况的版本。
  • 这是一种实现方式,但编写起来更复杂。在更一般的情况下,只需在所有情况下复制和分配。
  • 要么,要么永远不传递要存储的自动值;在对象范围之外动态分配它们并传入指针。

标签: c data-structures avl-tree


【解决方案1】:

要以自动存储持续时间存储指向数据的指针,是的,您必须知道容器中元素的大小并分配和复制指向的数据。

最简单的方法是在所有情况下只分配和复制,如有必要,可选择使用用户指定的clone()create() 函数进行深层复制。这还需要使用用户指定的destroy() 函数来正确处理副本(如有必要,再次处理)。

为了能够避免分配,你必须有某种状态变量,让你知道容器是否应该分配,或者只是复制指针值本身。

注意,这应该适用于 container 对象,而不是单个节点或元素。如果容器以一种或另一种方式存储数据,它应该以这种方式存储所有数据。见Principle of Least Astonishment

这是一种更复杂的方法,因为您必须确保使用正确的过程来根据状态变量添加和删除元素。确保您从不传递指向具有自动存储持续时间的值的指针通常要简单得多。

【讨论】:

    【解决方案2】:

    使用混合风格;例如不要让数据成为节点的一部分,而是让节点成为数据的一部分:

    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;
    };
    

    animal 的构造函数类似于

    struct animal *animal_create(double d)
    {
        struct animal *animal = malloc(sizeof *animal);
    
        *animal = (struct animal) {
            .node = AVL_NODE_INIT(),
            .dangerousness = d,
        };
    
        return animal;
    }
    

    通用的 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))
    {
        /* .... */
    }
    

    还有一个cmp 函数用于animal 之类的

    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;
    }
    

    【讨论】:

      猜你喜欢
      • 2016-09-21
      • 2023-04-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-03
      相关资源
      最近更新 更多