¿Qué es un puntero opaco en C?
¿Puedo conocer el uso y la lógica detrás del concepto de puntero opaco en C?
Un puntero opaco es aquel en el que no se revelan detalles de los datos subyacentes (de una definición del diccionario: opaco: adjetivo; no se puede ver a través de él; no transparente ).
Por ejemplo, puedes declarar en un archivo de encabezado (esto es de parte de mi código actual):
typedef struct pmpi_s *pmpi;
que declara un tipo pmpi
que es un puntero a la estructura struct pmpi_s
opaca , por lo tanto, cualquier cosa que declare pmpi
será un puntero opaco.
Los usuarios de esa declaración pueden escribir libremente código como:
pmpi xyzzy = NULL;
sin conocer la "definición" real de la estructura.
Luego, en el código que conoce la definición (es decir, el código que proporciona la funcionalidad para pmpi
el manejo), puede "definir" la estructura:
struct pmpi_s {
uint16_t *data; // a pointer to the actual data array of uint16_t.
size_t sz; // the allocated size of data.
size_t used; // number of segments of data in use.
int sign; // the sign of the number (-1, 0, 1).
};
y acceder fácilmente a sus campos individuales, algo que los usuarios del archivo de encabezado no pueden hacer.
Puede encontrar más información en la página de Wikipedia para obtener punteros opacos.
Su uso principal es ocultar los detalles de implementación a los usuarios de su biblioteca. La encapsulación (a pesar de lo que le dirá la gente de C++) existe desde hace mucho tiempo :-)
Desea publicar suficientes detalles sobre su biblioteca para que los usuarios puedan utilizarla de manera efectiva, y nada más. Publicar más proporciona a los usuarios detalles en los que pueden llegar a confiar (como el hecho de que la variable de tamaño sz
se encuentra en una ubicación específica de la estructura, lo que puede llevarlos a eludir sus controles y manipularlos directamente).
Entonces encontrarás a tus clientes quejándose amargamente cuando cambies las partes internas. Sin esa información de estructura, su API se limita solo a lo que usted proporciona y se mantiene su libertad de acción con respecto a los aspectos internos.
Los punteros opacos se utilizan en las definiciones de interfaces de programación (API).
Normalmente son indicadores de tipos de estructuras incompletas, declaradas como:
typedef struct widget *widget_handle_t;
Su propósito es proporcionar al programa cliente una forma de mantener una referencia a un objeto administrado por la API, sin revelar nada sobre la implementación de ese objeto, aparte de su dirección en la memoria (el puntero mismo).
El cliente puede pasar el objeto, almacenarlo en sus propias estructuras de datos y comparar dos de esos punteros, ya sean iguales o diferentes, pero no puede eliminar la referencia de los punteros para echar un vistazo a lo que hay en el objeto.
La razón por la que se hace esto es para evitar que el programa cliente se vuelva dependiente de esos detalles, de modo que la implementación pueda actualizarse sin tener que volver a compilar los programas cliente.
Debido a que los punteros opacos están escritos, existe una buena medida de seguridad de tipos. Si tenemos:
typedef struct widget *widget_handle_t;
typedef struct gadget *gadget_handle_t;
int api_function(widget_handle_t, gadget_handle_t);
Si el programa cliente confunde el orden de los argumentos, habrá un diagnóstico del compilador, porque a se struct gadget *
está convirtiendo en a struct widget *
sin conversión.
Esa es la razón por la que definimos struct
tipos que no tienen miembros; cada struct
declaración con una nueva etiqueta diferente introduce un nuevo tipo que no es compatible con struct
los tipos declarados previamente.
¿Qué significa que un cliente se vuelva dependiente? Supongamos que a widget_t
tiene propiedades de ancho y alto. Si no es opaco y se ve así:
typedef struct widget {
short width;
short height;
} widget_t;
entonces el cliente puede hacer esto para obtener el ancho y el alto:
int widget_area = whandle->width * whandle->height;
mientras que bajo el paradigma opaco, tendría que usar funciones de acceso (que no están integradas):
// in the header file
int widget_getwidth(widget_handle_t *);
int widget_getheight(widget_handle_t *);
// client code
int widget_area = widget_getwidth(whandle) * widget_getheight(whandle);
Observe cómo los widget
autores usaron el short
tipo para ahorrar espacio en la estructura y eso ha sido expuesto al cliente de la interfaz no opaca. Supongamos que los widgets ahora pueden tener tamaños que no encajan short
y la estructura tiene que cambiar:
typedef struct widget {
int width;
int height;
} widget_t;
El código del cliente debe volverse a compilar ahora para recoger esta nueva definición. Dependiendo de las herramientas y el flujo de trabajo de implementación, incluso puede existir el riesgo de que esto no se haga: el código de cliente antiguo intenta usar la nueva biblioteca y se comporta mal al acceder a la nueva estructura usando el diseño anterior. Eso puede suceder fácilmente con las bibliotecas dinámicas. La biblioteca se actualiza, pero los programas dependientes no.
El cliente que utiliza la interfaz opaca continúa funcionando sin modificaciones y, por lo tanto, no requiere recompilación. Simplemente llama a la nueva definición de las funciones de acceso. Estos están en la biblioteca de widgets y recuperan correctamente los nuevos int
valores escritos de la estructura.
Tenga en cuenta que, históricamente (y todavía actualmente aquí y allá) también ha habido una práctica mediocre de usar el void *
tipo como tipo de mango opaco:
typedef void *widget_handle_t;
typedef void *gadget_handle_t;
int api_function(widget_handle_t, gadget_handle_t);
Bajo este esquema, puedes hacer esto, sin ningún diagnóstico:
api_function("hello", stdout);
La API de Microsoft Windows es un ejemplo de un sistema en el que puede tener ambas cosas. De forma predeterminada, varios tipos de identificadores, como HWND
(identificador de ventana) y HDC
(contexto de dispositivo), son todos void *
. Entonces no hay seguridad de tipos; a HWND
podría pasarse donde HDC
se espera a, por error. Si haces esto:
#define STRICT
#include <windows.h>
luego, estos identificadores se asignan a tipos mutuamente incompatibles para detectar esos errores.
Opaco, como su nombre indica, es algo a través de lo que no podemos ver. Por ejemplo, la madera es opaca. Un puntero opaco es un puntero que apunta a una estructura de datos cuyo contenido no está expuesto en el momento de su definición.
Ejemplo:
struct STest* pSTest;
Es seguro asignarlo NULL
a un puntero opaco.
pSTest = NULL;