Ocultar miembros en una estructura C


He estado leyendo sobre OOP en C, pero nunca me gustó cómo no puedes tener miembros de datos privados como puedes en C++. Pero entonces vino a mi mente que se podía crear 2 estructuras. Uno se define en el archivo de cabecera y el otro es definido en el archivo de origen.

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

Desde aquí solo puedes lanzar una estructura a la otra. ¿Se considera esto una mala práctica? O se hace a menudo?

Author: Marlon, 2010-04-20

13 answers

Personalmente, me gustaría más esto:

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

Es C después de todo, si la gente quiere meter la pata, se les debe permitir - no hay necesidad de ocultar cosas, excepto:

Si lo que necesitas es mantener la ABI/API compatible, hay 2 enfoques que son más comunes de lo que he visto.

  • No dé a sus clientes acceso a la estructura, déles un identificador opaco (un void* con un nombre bonito), proporcione funciones de inicio/destrucción y de acceso para todo. Esto se asegura de que pueda cambio la estructura sin siquiera recompilar los clientes si estás escribiendo una biblioteca.

  • Proporcione un identificador opaco como parte de su estructura, que puede asignar como desee. Este enfoque se utiliza incluso en C++ para proporcionar compatibilidad con ABI.

E. g

 struct SomeStruct {
  int member;
  void* internals; //allocate this to your private struct
 };
 31
Author: nos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-20 09:25:04

sizeof(SomeStruct) != sizeof(SomeStructSource). Esto hará que alguien te encuentre y te asesine algún día.

 43
Author: hobbs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-20 01:47:23

Casi lo tienes, pero no has ido lo suficientemente lejos.

En el encabezado:

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

En el .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 ...

El punto es, aquí ahora los consumidores tienen no conocimiento de los aspectos internos de SomeStruct, y puede cambiarlo con impunidad, agregando y eliminando miembros a voluntad, incluso sin que los consumidores necesiten recompilar. Tampoco pueden" accidentalmente " munge miembros directamente, o asignar SomeStruct en la pila. Esto, por supuesto, también puede ser visto como una desventaja.

 24
Author: Logan Capaldo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-13 22:38:18

No recomiendo usar el patrón de estructura pública. El patrón de diseño correcto, para OOP en C, es proporcionar funciones para acceder a todos los datos, nunca permitiendo el acceso público a los datos. Los datos de la clase deben declararse en la fuente, a fin de que sean privados, y ser referenciados de manera directa, donde Create y Destroy hace asignación y libre de los datos. De esta manera, el dilema público/privado ya no existirá.

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

En el otro lado, si no desea utilizar Malloc / Free (que puede ser una sobrecarga innecesaria para algunas situaciones) Le sugiero que oculte la estructura en un archivo privado. Los miembros privados serán accesibles, pero que en la participación del usuario.

/*********** 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 */
}
 13
Author: Felipe Lavratti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-26 19:51:08

Nunca hagas eso. Si su API admite cualquier cosa que tome SomeStruct como parámetro (lo cual espero que haga), entonces podrían asignar uno en una pila y pasarlo. Obtendría errores mayores al intentar acceder al miembro privado ya que el que el compilador asigna a la clase cliente no contiene espacio para él.

La forma clásica de ocultar miembros en una estructura es convertirla en un void*. Básicamente es un identificador / cookie que solo conocen sus archivos de implementación. Bastante cada biblioteca de C hace esto para datos privados.

 9
Author: NG.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-20 01:50:36

Algo similar al método que usted ha propuesto se utiliza de hecho a veces (por ejemplo. vea las diferentes variables de struct sockaddr* en la API de sockets de BSD), pero es casi imposible de usar sin violar las estrictas reglas de aliasing de C99.

Usted puede, sin embargo, hacerlo con seguridad :

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
Author: caf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-20 02:11:03

Escribiría una estructura oculta, y la referenciaría usando un puntero en la estructura pública. Por ejemplo, su .h podría tener:

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

Y su .c:

typedef struct {
    int c, d;
} private_t;

Obviamente no protege contra la aritmética de punteros, y agrega un poco de sobrecarga para la asignación/desasignación, pero supongo que está más allá del alcance de la pregunta.

 4
Author: jweyrich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-20 02:18:46

Utilice la siguiente solución:

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

El resultado es:

t.a = 1
t.x = 2
tref->a = 3
tref->x = 4
 3
Author: Ilya Matveychikov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-05 20:40:51

Hay mejores maneras de hacer esto, como usar un puntero void * a una estructura privada en la estructura pública. La forma en que lo estás haciendo estás engañando al compilador.

 2
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-20 01:51:43

Este enfoque es válido, útil, estándar C.

Un enfoque ligeramente diferente, utilizado por sockets API, que fue definido por BSD Unix, es el estilo utilizado para struct sockaddr.

 1
Author: Heath Hunnicutt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-20 01:57:04

No es muy privado, dado que el código de llamada puede devolverse a un (SomeStructSource *). Además, ¿qué sucede cuando desea agregar otro miembro público? Tendrás que romper la compatibilidad binaria.

EDITAR: Me perdí que estaba en a .archivo c, pero realmente no hay nada que impida que un cliente lo copie, o posiblemente incluso #includeing el .c archivo directamente.

 0
Author: Matthew Flaschen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-20 01:48:52

Relacionado, aunque no exactamente oculto.

Es deprecar condicionalmente a los miembros.

Tenga en cuenta que esto funciona para GCC / Clang, pero MSVC y otros compiladores también pueden desaprovechar, así que es posible llegar a una versión más portátil.

Si compila con advertencias bastante estrictas, o advertencias como errores, esto al menos evita el uso accidental.

// =========================================
// 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
Author: ideasman42,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-02 21:20:57

Mi solución sería proporcionar solo el prototipo de la estructura interna y luego declarar la definición en el .archivo c. Muy útil para mostrar la interfaz de C y usar C++ detrás.

.h:

struct internal;

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

.c:

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

Nota: En ese caso, la variable tiene que ser un puntero porque el compilador no puede saber el tamaño de la estructura interna.

 0
Author: adc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-24 16:07:46