Hiding members in a C struct

2019-01-10 08:38发布

I've been reading about OOP in C but I never liked how you can't have private data members like you can in C++. But then it came to my mind that you could create 2 structures. One is defined in the header file and the other is defined in the source file.

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

From here you can just cast one structure to the other. Is this considered bad practice? Or is it done often?

13条回答
爱情/是我丢掉的垃圾
2楼-- · 2019-01-10 09:15

Use the following workaround:

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

    t.a = 1;
    C_PRIV(t).x = 2;

    printf("t.a = %d\nt.x = %d\n", t.a, 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;
}

Result is:

t.a = 1
t.x = 2
tref->a = 3
tref->x = 4
查看更多
霸刀☆藐视天下
3楼-- · 2019-01-10 09:16

This approach is valid, useful, standard C.

A slightly different approach, used by sockets API, which was defined by BSD Unix, is the style used for struct sockaddr.

查看更多
Emotional °昔
4楼-- · 2019-01-10 09:20

You almost have it, but haven't gone far enough.

In the header:

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

In the .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 ...

The point is, here now consumers have no knowledge of the internals of SomeStruct, and you can change it with impunity, adding and removing members at will, even without consumers needing to recompile. They also can't "accidentally" munge members directly, or allocate SomeStruct on the stack. This of course can also be viewed as a disadvantage.

查看更多
爷、活的狠高调
5楼-- · 2019-01-10 09:21

sizeof(SomeStruct) != sizeof(SomeStructSource). This will cause someone to find you and murder you someday.

查看更多
走好不送
6楼-- · 2019-01-10 09:24

I'd write a hidden structure, and reference it using a pointer in the public structure. For example, your .h could have:

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

And your .c:

typedef struct {
    int c, d;
} private_t;

It obviously doesn't protect against pointer arithmetic, and adds a bit of overhead for allocation/deallocation, but I guess it's beyond the scope of the question.

查看更多
forever°为你锁心
7楼-- · 2019-01-10 09:26

Something similar to the method you've proposed is indeed used sometimes (eg. see the different varities of struct sockaddr* in the BSD sockets API), but it's almost impossible to use without violating C99's strict aliasing rules.

You can, however, do it safely:

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;
}
查看更多
登录 后发表回答