使用`const`元素设计C容器?

ste*_*fan 6 c containers const interface

容器接口是否应该将包含元素的指针声明为const

任务:用C设计一个容器(注意:这显然是关于普通的C, 不是关于C++,也不是C#).容器将被指向项目的指针,并应返回指向项目的指针.

有点无意义的MWE,但这个想法也扩展到有用的容器:

#include <stdio.h>

typedef struct {
    void *data;
} container_t;

void put(container_t *c, void *data)
{
    c->data = data;
}

void *get(container_t *c)
{
    return c->data;
}

int main(void)
{
    container_t c;
    int x = 42;
    int *y;

    put(&c, &x);
    y = get(&c);

    printf("value = %d\n", *y);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.

好吧,容器不应该使用指针来修改存储的数据.我想通过一个小小的改变在界面中明确这一点:

void put(container_t *c, const void *data)
                         ^new
Run Code Online (Sandbox Code Playgroud)

现在编译器要求我做另一个更改,我真的同意这个:

typedef struct { const void *data; } container_t;
                 ^new
Run Code Online (Sandbox Code Playgroud)

然后编译器要求我再做一次更改,这也很合乎逻辑:

const void *get(container_t *c)
^new
Run Code Online (Sandbox Code Playgroud)

现在,编译器抱怨y不存在const int *,这让我有点不高兴.处理这个问题的正确方法是什么?

Jen*_*edt 2

您的接口正在制定一个契约,即传递给它的对象永远不会通过该访问被修改。因此,本质上,您需要两种容器类型,或者更一般地说是接口,一种假设对象是可变的(版本void*),另一种假设对象可以修改(版本void const*)。

这是一个在很多地方都会出现的问题,甚至在 C 库中也是如此。例如memchr,这样一个接口默默地进行const对象的转换。在现代 C 中可以通过 来规避这种直接的叛逆行为_Generic

存储该值然后在代码中完全不相关的位置重现它的问题是无法避免的。C 和 POSIX 标准对于tss_set/tss_get和存在这个问题pthread_setspecific/pthread_getspecific,并且以不兼容的方式解决该问题。C 变体用于void*两者,POSIX 变体用于void const*设置和void*获取。