隐藏C结构中的成员

Mar*_*lon 61 c struct private-members

我一直在读C中的OOP,但我从来不喜欢你不能在C++中拥有像你这样的私人数据成员.但后来我想到你可以创建2个结构.一个在头文件中定义,另一个在源文件中定义.

// =========================================
// 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;
}
Run Code Online (Sandbox Code Playgroud)

从这里你可以将一个结构投射到另一个结构.这被认为是不好的做法吗?还是经常这样做?

hob*_*bbs 47

sizeof(SomeStruct) != sizeof(SomeStructSource).这导致有人找到你并有一天谋杀你.

  • "总是编码,好像最终维护你的代码的人是一个知道你住在哪里的暴力精神病患者." (归功于里克奥斯本) (20认同)
  • 任何陪审团都会让他们事后离开. (19认同)

nos*_*nos 36

就个人而言,我更喜欢这样:

typedef struct {
  int _public_member;
  /*I know you wont listen, but don't ever touch this member.*/
  int _private_member;
} SomeStructSource;
Run Code Online (Sandbox Code Playgroud)

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

如果您需要保持ABI/API兼容,那么有两种方法比我看到的更常见.

  • 不要让你的客户端访问struct,给他们一个不透明的句柄(一个带有漂亮名字的void*),为所有东西提供init/destroy和accessor函数.如果您正在编写库,这可确保您无需重新编译客户端即可更改结构.

  • 提供一个不透明的句柄作为你的结构的一部分,你可以随意分配.这种方法甚至在C++中用于提供ABI兼容性.

例如

 struct SomeStruct {
  int member;
  void* internals; //allocate this to your private struct
 };
Run Code Online (Sandbox Code Playgroud)

  • @fanl在C中这样做有很多含义,例如以这种方式隐藏结构,在堆栈上分配它或者作为另一个结构的成员内联变得非常困难.简单的方法是动态分配结构,只暴露一个void*或句柄,虽然在某些情况下这样做可能没问题,但是很多情况下其含义太大而且会阻碍你利用C为您提供的. (3认同)
  • 我发现一个非常糟糕的设计,允许客户端访问结构的任何成员。整个结构应该是私有的。对其成员的访问应该通过 getter 和 setter 来完成。 (2认同)

Log*_*ldo 25

你几乎拥有它,但还远远不够.

在标题中:

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);
Run Code Online (Sandbox Code Playgroud)

在.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 ...
Run Code Online (Sandbox Code Playgroud)

问题的关键是,这里现在的消费者有没有 SomeStruct的内部的知识,你可以肆无忌惮地改变它,添加和随意删除成员,即使没有消费者无需重新编译.他们也不能"意外地"直接攻击成员,或者在堆栈上分配SomeStruct.这当然也可以被视为一个缺点.

  • 有些人认为使用`typedef`来隐藏指针是一个坏主意,特别是因为更明显的是`SomeStruct*`需要以某种方式释放,而不是像SomeThing`那样,它看起来像普通的堆栈变量.实际上,您仍然可以声明`struct SomeStruct;`,并且,只要您不定义它,人们将被迫使用`SomeStruct*'指针而不能解除其成员的引用,因此具有相同的效果而不隐藏指针. (16认同)

Fel*_*tti 17

我不建议使用公共结构模式.对于C中的OOP,正确的设计模式是提供访问每个数据的功能,从不允许公共访问数据.类数据应该在源处声明,以便是私有的,并以前向方式引用,其中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 */
Run Code Online (Sandbox Code Playgroud)

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

/*********** 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 */
}
Run Code Online (Sandbox Code Playgroud)

  • 这种方法的一个困难是它需要使用 malloc/free,即使在结构应该可以简单地创建为堆栈变量然后在方法退出时消失的情况下也是如此。如果在创建/销毁之间其他代码需要创建持久对象,则对应该具有堆叠语义的事物使用 malloc/free 可能会导致内存碎片。如果提供一种方法来使用传入的存储块来保存对象,并为此目的键入合适大小的 int[],则可以缓解此类问题。 (2认同)
  • 尽管我喜欢 C 的简单性,但在应用设计模式时却很烦人。C 和设计模式根本不兼容。令人沮丧的是,在 C 存在 40 年后,没有一种技术可以让您利用 C 中的最佳实践编码规则。如果我们忽略堆栈分配问题,ADT 技术确实可以使用,但前提是会有一个适当的 malloc 实现,它不会导致任何碎片问题。我真的很惊讶没有标准 C 库实现<待续> (2认同)

NG.*_*NG. 9

永远不要那样做.如果您的API支持任何需要SomeStruct作为一个参数(我很期待它),那么他们可能会在栈上分配一个,并通过它,你会得到重大失误试图访问私有成员,因为一个编译器为客户端类分配的不包含空间.

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


caf*_*caf 7

有时会使用与您提出的方法类似的东西(例如,查看struct sockaddr*BSD套接字API中的不同变量),但在不违反C99的严格别名规则的情况下几乎不可能使用.

但是,您可以安全地执行此操作:

somestruct.h:

struct SomeStructPrivate; /* Opaque type */

typedef struct {
  int _public_member;
  struct SomeStructPrivate *private;
} SomeStruct;
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)