wake-up-neo.com

Mitglieder in einer C-Struktur ausblenden

Ich habe über OOP in C gelesen, aber ich habe nie gemocht, dass Sie keine privaten Datenmitglieder haben können, wie Sie es in C++ können. Dann fiel mir ein, dass man 2 Strukturen schaffen kann. Eine ist in der Headerdatei definiert und die andere ist in der Quelldatei definiert.

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

Von hier aus können Sie einfach eine Struktur in die andere umwandeln ... Ist das eine schlechte Praxis? Oder wird es oft gemacht?

56
Marlon

Persönlich würde ich eher so sein: 

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

Es ist immerhin C, wenn die Leute es vermasseln wollen, sollte es ihnen erlaubt sein - nichts zu verbergen, außer:

Wenn Sie die ABI/API-Kompatibilität beibehalten möchten, gibt es zwei Ansätze, die häufiger sind als ich.

  • Geben Sie Ihren Kunden keinen Zugriff auf die Struktur, geben Sie ihnen ein undurchsichtiges Handle (ein leeres * mit einem hübschen Namen), stellen Sie Init/Destroy- und Accessor-Funktionen für alles bereit. Damit stellen Sie sicher, dass Sie die Struktur ändern können, ohne die Clients neu kompilieren zu müssen, wenn Sie eine Bibliothek schreiben.

  • stellen Sie als Teil Ihrer Struktur ein undurchsichtiges Handle bereit, das Sie beliebig zuordnen können. Dieser Ansatz wird sogar in C++ verwendet, um ABI-Kompatibilität zu gewährleisten.

z.B

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

sizeof(SomeStruct) != sizeof(SomeStructSource). Dies wird veranlassen, dass jemand dich findet und dich eines Tages ermordet.

46
hobbs

Sie haben es fast, sind aber nicht weit genug gegangen. 

In der Kopfzeile:

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 der 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 ...

Der Punkt ist, jetzt haben die Verbraucher keine Kenntnis von den Internen von SomeStruct, und Sie können dies ungestraft ändern, indem Sie nach Belieben Mitglieder hinzufügen und entfernen, auch wenn die Verbraucher nicht neu kompilieren müssen. Sie können auch nicht "versehentlich" Munge-Member direkt ordnen oder SomeStruct auf dem Stack zuordnen. Dies kann natürlich auch als Nachteil angesehen werden.

25
Logan Capaldo

Ich empfehle nicht die Verwendung des öffentlichen Strukturmusters. Das richtige Entwurfsmuster für OOP in C besteht darin, Funktionen für den Zugriff auf alle Daten bereitzustellen, wobei der öffentliche Zugriff auf Daten niemals gestattet wird. Die Klassendaten sollten an der Quelle deklariert werden, um privat zu sein, und auf Vorwärtsrichtung referenziert werden, wobei Create und Destroy die Zuweisung vornehmen und von den Daten frei sind. Auf diese Weise gibt es das öffentliche/private Dilemma nicht mehr.

/*********** 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 */

Auf der anderen Seite, wenn Sie Malloc/Free nicht verwenden möchten (was in manchen Situationen unnötig ist), schlage ich vor, dass Sie die Struktur in einer privaten Datei verstecken. Auf private Mitglieder kann zugegriffen werden, dies jedoch auf Benutzerwunsch. 

/*********** 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 */
}
15
Felipe Lavratti

TU das niemals. Wenn Ihre API etwas unterstützt, das SomeStruct als Parameter verwendet (was ich erwarte, dass dies der Fall ist), könnten sie einen auf einem Stack zuweisen und ihn übergeben. Sie erhalten große Fehler beim Zugriff auf das private Member seit dem Compiler für die Client-Klasse reserviert enthält nicht Platz für sie.

.__ Die klassische Methode, Mitglieder in einer Struktur auszublenden, besteht darin, sie als ungültig zu machen *. Es ist im Grunde ein Handle/Cookie, über das nur Ihre Implementierungsdateien Bescheid wissen. So ziemlich jede C-Bibliothek macht dies für private Daten.

9
NG.

Etwas, das der von Ihnen vorgeschlagenen Methode ähnelt, wird in der Tat manchmal verwendet (siehe z. B. die verschiedenen Varianten von struct sockaddr* in der BSD-Sockets-API), aber es ist fast unmöglich, es zu verwenden, ohne die strengen Aliasing-Regeln von C99 zu verletzen.

Sie können es jedoch sicher machen:

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

Ich würde eine versteckte Struktur schreiben und mit einem Zeiger in der öffentlichen Struktur darauf verweisen. Zum Beispiel könnte Ihre .h Folgendes haben:

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

Und deine .c:

typedef struct {
    int c, d;
} private_t;

Offensichtlich schützt es nicht vor Zeigerarithmetik und fügt ein wenig Overhead für die Zuordnung/Deallocation hinzu.

4
jweyrich

Verwenden Sie die folgende Problemumgehung:

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

Ergebnis ist:

t.a = 1
t.x = 2
tref->a = 3
tref->x = 4
3

Es gibt bessere Möglichkeiten, dies zu tun, beispielsweise einen void *-Zeiger auf eine private Struktur in der öffentlichen Struktur. Auf die Art und Weise, wie Sie es tun, täuschen Sie den Compiler.

2
user181548

Dieser Ansatz ist gültig, nützlich, Standard C. 

Ein etwas anderer Ansatz, der von der Sockets-API verwendet wird, der von BSD Unix definiert wurde, ist der für struct sockaddr verwendete Stil.

1
Heath Hunnicutt

Nicht sehr privat, da der aufrufende Code in einen (SomeStructSource *) umgewandelt werden kann. Was passiert auch, wenn Sie ein anderes öffentliches Mitglied hinzufügen möchten? Sie müssen die binäre Kompatibilität unterbrechen.

BEARBEITEN: Ich habe vermisst, dass es sich in einer .c-Datei befand, aber es gibt wirklich nichts, was einen Client daran hindert, sie zu kopieren oder sogar die #include-Datei direkt zu kopieren. 

0

Verwandt, aber nicht genau versteckt.

Ist, Mitglieder bedingt abzulehnen.

Beachten Sie, dass dies für GCC/Clang funktioniert, aber MSVC und andere Compiler können ebenfalls veraltet sein,. Damit kann eine tragbarere Version erstellt werden.

Wenn Sie mit ziemlich strengen Warnungen oder Warnungen als Fehler erstellen, wird dadurch zumindest eine versehentliche Verwendung vermieden.

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

Meine Lösung wäre, nur den Prototyp der internen Struktur bereitzustellen und dann die Definition in der C-Datei zu deklarieren. Sehr nützlich, um die C-Schnittstelle anzuzeigen und hinter C++ zu verwenden.

.h:

struct internal;

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

.c:

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

Anmerkung: In diesem Fall muss die Variable ein Zeiger sein, da der Compiler die Größe der internen Struktur nicht kennen kann.

0
adc