隐藏C结构中的成员

我一直在阅读关于面向对象的C语言,但是我从来不喜欢在C ++中不能拥有私人数据成员。 但后来我想到你可以创build2个结构。 一个在头文件中定义,另一个在源文件中定义。

// ========================================= // in somestruct.h typedef struct { int _public_member; } SomeStruct; // ========================================= // in somestruct.c #include "somestruct.h" typedef struct { int _public_member; int _private_member; } SomeStructSource; SomeStruct *SomeStruct_Create() { SomeStructSource *p = (SomeStructSource *)malloc(sizeof(SomeStructSource)); p->_private_member = 42; return (SomeStruct *)p; } 

从这里你可以把一个结构投给另一个。 这被认为是不好的做法? 还是经常做?

我个人更喜欢这样的:

 typedef struct { int _public_member; /*I know you wont listen, but don't ever touch this member.*/ int _private_member; } SomeStructSource; 

毕竟,如果人们想搞砸的话,他们应该被允许 – 不需要隐藏东西,除了:

如果你需要的是保持ABI / API兼容,那么有两种方法比我所见的更为常见。

  • 不要让你的客户访问这个结构体,给它们一个不透明的句柄(一个名字相当的void *),为每个事物提供init / destroy和accessor函数。 这可以确保你可以改变结构,而无需重新编译客户端,如果你正在编写一个库。

  • 提供一个不透明的句柄作为你的结构的一部分,你可以分配,但是你喜欢。 这种方法甚至用于C ++来提供ABI兼容性。

例如

  struct SomeStruct { int member; void* internals; //allocate this to your private struct }; 

sizeof(SomeStruct) != sizeof(SomeStructSource) 。 这导致某人find你,并在某天杀死你。

你几乎已经拥有了,但还没有走得够远。

在标题中:

 struct SomeStruct; typedef struct SomeStruct *SomeThing; SomeThing create_some_thing(); destroy_some_thing(SomeThing thing); int get_public_member_some_thing(SomeThing thing); void set_public_member_some_thing(SomeThing thing, int value); 

在.c中:

 struct SomeStruct { int public_member; int private_member; }; SomeThing create_some_thing() { SomeThing thing = malloc(sizeof(*thing)); thing->public_member = 0; thing->private_member = 0; return thing; } ... etc ... 

重点是,现在消费者不了解SomeStruct的内部知识,您可以随意更改,即使没有消费者需要重新编译,也可以随意添加和删除成员。 他们也不能直接“意外”成员,或者在堆栈上分配SomeStruct。 这当然也可以被看作是一个缺点。

我不推荐使用公共结构模式。 对于C中的OOP,正确的devise模式是提供访问每个数据的function,从不允许公众访问数据。 类数据应该在源头声明,以便是私有的,并以正向的方式引用,其中CreateDestroy分配和释放数据。 这样,公共/私人的困境就不会再存在了。

 /*********** header.h ***********/ typedef struct sModuleData module_t' module_t *Module_Create(); void Module_Destroy(module_t *); /* Only getters and Setters to access data */ void Module_SetSomething(module_t *); void Module_GetSomething(module_t *); /*********** source.c ***********/ struct sModuleData { /* private data */ }; module_t *Module_Create() { module_t *inst = (module_t *)malloc(sizeof(struct sModuleData)); /* ... */ return inst; } void Module_Destroy(module_t *inst) { /* ... */ free(inst); } /* Other functions implementation */ 

另一方面,如果你不想使用Malloc / Free(在某些情况下这可能是不必要的开销),我build议你将结构隐藏在私有文件中。 私人会员将可以访问,但在用户的利益。

 /*********** privateTypes.h ***********/ /* All private, non forward, datatypes goes here */ struct sModuleData { /* private data */ }; /*********** header.h ***********/ #include "privateTypes.h" typedef struct sModuleData module_t; void Module_Init(module_t *); void Module_Deinit(module_t *); /* Only getters and Setters to access data */ void Module_SetSomething(module_t *); void Module_GetSomething(module_t *); /*********** source.c ***********/ void Module_Init(module_t *inst) { /* perform initialization on the instance */ } void Module_Deinit(module_t *inst) { /* perform deinitialization on the instance */ } /*********** main.c ***********/ int main() { module_t mod_instance; module_Init(&mod_instance); /* and so on */ } 

永远不要这样做。 如果你的API支持任何需要SomeStruct作为参数的东西(我期待它),那么他们可以在堆栈中分配一个并传入它。你会遇到重大的错误,试图访问私有成员,因为编译器为客户端分配的类不包含空间。

隐藏结构中成员的经典方法是使其成为void *。 这基本上是一个句柄/ cookie,只有你的实现文件知道。 几乎每个C库都会为私有数据执行此操作。

类似于你所提出的方法有时候确实有用(例如,见BSD套接字API中struct sockaddr*的不同变struct sockaddr* ),但是在不违反C99严格的别名规则的情况下使用几乎是不可能的。

但是,您可以安全地做到这一点:

somestruct.h

 struct SomeStructPrivate; /* Opaque type */ typedef struct { int _public_member; struct SomeStructPrivate *private; } SomeStruct; 

somestruct.c

 #include "somestruct.h" struct SomeStructPrivate { int _member; }; SomeStruct *SomeStruct_Create() { SomeStruct *p = malloc(sizeof *p); p->private = malloc(sizeof *p->private); p->private->_member = 0xWHATEVER; return p; } 

我会写一个隐藏的结构,并在公共结构中使用指针来引用它。 例如,你的.h可以有:

 typedef struct { int a, b; void *private; } public_t; 

而你的.c:

 typedef struct { int c, d; } private_t; 

它显然不能防止指针算术,并增加了一些分配/释放的开销,但我想这是超出了问题的范围。

有更好的方法来做到这一点,比如使用一个void *指向公共结构中的私有结构。 你这样做的方式是愚弄编译器。

使用以下解决方法:

 #include <stdio.h> #define C_PRIVATE(T) struct T##private { #define C_PRIVATE_END } private; #define C_PRIV(x) ((x).private) #define C_PRIV_REF(x) (&(x)->private) struct T { int a; C_PRIVATE(T) int x; C_PRIVATE_END }; int main() { struct T t; struct T *tref = &t; ta = 1; C_PRIV(t).x = 2; printf("ta = %d\nt.x = %d\n", ta, C_PRIV(t).x); tref->a = 3; C_PRIV_REF(tref)->x = 4; printf("tref->a = %d\ntref->x = %d\n", tref->a, C_PRIV_REF(tref)->x); return 0; } 

结果是:

 ta = 1 tx = 2 tref->a = 3 tref->x = 4 

这种方法是有效的,有用的,标准C.

由BSD Unix定义的套接字API所使用的稍微不同的方法是用于struct sockaddr的样式。

不是非常私人的,因为调用代码可以转换回(SomeStructSource *) 。 另外,当你想添加另一个公共成员时会发生什么? 你将不得不打破二进制兼容性。

编辑:我错过了它在一个.c文件,但真的没有什么阻止客户端复制出来,甚至可能甚至#include直接.c文件。

相关,虽然不完全隐藏。

是有条件地弃用成员。

请注意,这适用于GCC / Clang,但MSVC和其他编译器也可能不赞同,因此可能会想出更便携的版本。

如果用相当严格的警告或警告作为错误来构build,至less可以避免意外使用。

 // ========================================= // in somestruct.h #ifdef _IS_SOMESTRUCT_C # if defined(__GNUC__) # define HIDE_MEMBER __attribute__((deprecated)) # else # define HIDE_MEMBER /* no hiding! */ # endif #else # define HIDE_MEMBER #endif typedef struct { int _public_member; int _private_member HIDE_MEMBER; } SomeStruct; #undef HIDE_MEMBER // ========================================= // in somestruct.c #define _IS_SOMESTRUCT_C #include "somestruct.h" SomeStruct *SomeStruct_Create() { SomeStructSource *p = (SomeStructSource *)malloc(sizeof(SomeStructSource)); p->_private_member = 42; return (SomeStruct *)p; } 

我的解决scheme是只提供内部结构的原型,然后在.c文件中声明定义。 显示C接口并在后面使用C ++非常有用。

。H :

 struct internal; struct foo { int public_field; struct internal *_internal; }; 

。C :

 struct internal { int private_field; // could be a C++ class }; 

注意:在这种情况下,variables必须是一个指针,因为编译器无法知道内部结构的大小。