2010-09-23 14 views
14

Duplicar posible:
Typedef pointers a good idea?¿El tipo es un tipo de puntero considerado una mala práctica?

que he visto esta rareza en muchas API he utilizado:

typedef type_t *TYPE; 

Mi punto es que se declara una variable de tipo TYPE no dejará en claro que, de hecho, se ha declarado un puntero.

¿Usted, como yo, piensa que esto trae mucha confusión? ¿Esto es para hacer cumplir la encapsulación, o también hay otras razones? ¿Consideras que esto es una mala práctica?

+0

Confusiones Confusiones! –

+1

Solo una de las respuestas a "punteros Typedef una buena idea?" alude a la interacción inútil con 'const' - y esa respuesta está clasificada en un nivel bajo. En mi humilde opinión, el problema de "const" es el problema más importante con este tipo de tipos de archivos, ... por lo tanto, no es realmente un duplicado. –

+0

@John: No estoy de acuerdo con usted conclusión. Ha notado que una respuesta añadida mucho después de que se formuló la pregunta no recibió mucha atención, pero las preguntas siguen siendo fundamentalmente las mismas. – dmckee

Respuesta

22

En general, es una mala práctica.El problema importante es que no juega bien con const:

typedef type_t *TYPE; 
extern void set_type(TYPE t); 

void foo(const TYPE mytype) { 
    set_type(mytype); // Error expected, but in fact compiles 
} 

Para que el autor de foo() para expresar lo que realmente quieren decir, la biblioteca que ofrece TYPE también debe proporcionar CONST_TYPE:

typedef const type_t *CONST_TYPE; 

para que foo() pueda tener la firma void foo(CONST_TYPE mytype), y en este punto hemos descendido a farsa.

lo tanto una regla de oro:

Hacer typedefs de estructuras (en particular estructuras incompletas), no punteros a esas estructuras.

Si la definición de la estructura subyacente es no estar a disposición del público (que es a menudo loable), a continuación, que la encapsulación debe ser suministrada por el struct ser incompleta, en lugar de por typedefs inconvenientes:

struct type_t; 
typedef struct type_t type_t; 

void set_type(type_t *); 
int get_type_field(const type_t *); 
+0

Es interesante que algunos tipos no parecen necesitar una versión const. Por ejemplo, nunca he sentido ningún deseo de un 'const_pthread_t' en el que pueda llamar' pthread_getschedparam' pero no 'setschedparam'. 'const pthread_t' no logra eso (por supuesto), del mismo modo que el puntero typedefed no lo logra en su ejemplo. Y 'ftell' toma' FILE * ', no' const FILE * ', aunque supongo que debería ser const, para soportar el uso seguro de FILE *. Entonces, creo que este problema 'const' es significativo/decisivo en algunos casos, pero no siempre. El legado anterior a la conquista de C, tal vez? O simplemente pereza :-) –

+0

De hecho. Miré para ver si algo en el estándar había sido adaptado con 'const FILE *', pero ... no tanto. (¡Y estaba yo esperando ayudarte a entender 'FILE *'! :-)) Tal vez tiene que ver con si algo se siente como un tipo atómico, uno tampoco escribe 'const int'. O algo así como un problema de huevo y gallina: si ninguna de las funciones de indagación toma un 'const FILE *', ¿por qué escribirías tu propia función 'foo()' para tomar uno, cómo usarías tu parámetro? –

+1

'ftell' no puede tomar' const FILE * 'porque (en POSIX o cualquier otra implementación con hilos) debe obtener un bloqueo mutex en el objeto' FILE' a menos que la posición actual pueda obtenerse con una sola operación atómica. La idea de un 'const FILE *' no tiene sentido; no podría ser usado para nada. –

6

Una expresión común es el sufijo del tipo con _p para indicar que se trata de un puntero sin perder las cualidades del puntero.

A veces es necesario utilizar solo el tipo de puntero si la estructura a la que apunta no está públicamente disponible. Esto ayuda a facilitar el ocultamiento de datos. Es decir.

typedef struct hidden_secret_object * object; 
void change_object(object foo); 

esto le permite cambiar la forma en que está estructurado hidden_secret_object sin romper el código externo.

+2

¿Por qué necesita el puntero en el typedef para ocultar datos? 'typedef struct h_s_o objeto; void change_object (object * foo); 'lograría lo mismo. El ejemplo parece irrelevante para la pregunta. – schot

+0

A veces, un tipo está destinado a ser opaco, y en diferentes sistemas se usaría un tipo diferente. En algunos sistemas, este tipo podría ser grande, por lo que pasarlo por un puntero es más eficiente, mientras que en otros sistemas puede ser simplemente un token pasado como un entero. – nategoose

0

Tal vez una manera de hacerla más específica sería llamar al nuevo tipo de puntero type_ptr o algo por el estilo:

typedef type_t* type_ptr; 
5

No resulta claro tampoco. Tampoco me gustan los tipos de letra mayúscula completa (intento reservarlos para #defines).

De esta manera, es fácil engañarse al pensar que es de hecho un tipo de valor, mientras que estamos hablando de un tipo de puntero. El tipo de puntero puede abstraerse por completo con punteros inteligentes, pero eso no es una práctica común en C.

Sufrir con (como se mencionó anteriormente) _p, _ptr, puntero o cualquier cosa a lo largo de esas líneas crea claridad; aumenta el tipeo, eso es cierto, pero evitará que cometas errores tontos (como usar '.' en lugar de '->', ...) que te costará un valioso tiempo de desarrollo.

1

No creo que sea una mala práctica si se trata de un puntero a un tipo incompleto, o si por alguna otra razón no se espera que el usuario lo desreferencia. Nunca entendí FILE*.

Tampoco creo que sea una mala práctica si lo haces porque tienes varios niveles de direccionamiento indirecto, y quieres usarlo en situaciones donde algunos de ellos son irrelevantes. typedef char **argarray, o algo así.

Si se espera que el usuario lo desreferencia, entonces en C, creo que probablemente sea mejor retener el *. En C++, las personas están acostumbradas a los tipos definidos por el usuario con operator* sobrecargado, como los iteradores. En C eso no es normal.

0

Los calificadores de clase de almacenamiento como 'const' funcionarán de manera diferente con los punteros typedef'ed que con los 'naturales'. Si bien esto no suele ser bueno con 'const', puede ser muy útil con clases de almacenamiento específicas del compilador como "xdata". Una declaración como:

 
xdata WOKKA *foo; 

declarará "foo" sea un puntero, almacenada en la clase de almacenamiento por defecto, a un wokka en XDATA. Una declaración:

 
xdata WOKKA_PTR bar; 
declararía "bar" como un puntero, almacenado en xdata, en un WOKKA en cualquier clase de almacenamiento especificada en WOKKA_PTR. Si las rutinas de la biblioteca esperan punteros a cosas con una clase de almacenamiento particular, puede ser útil definir esas clases de almacenamiento dentro de los tipos de punteros.

0

Me ha mordido en el culo de vez en cuando:

for (vector<typedef_name_that_doesnt_indicate_pointerness_at_all>::iterator it; 
    it != v.end(); ++it) 
{ 
    it->foo(); // should have been written (*it)->foo(); 
} 

El único momento en que es aceptable es si el tipo está destinado a ser verdaderamente opaco y no accede directamente en absoluto. IOW, si alguien va a tener que desreferenciarlo fuera de una API, entonces la puntería no debe ocultarse detrás de un typedef.

4

Depende de lo que esté tratando de lograr. No hay una respuesta significativa "sí o no" a su pregunta de la forma en que se establece.

  • Si usted está tratando de crear una mango abstracta tipo de tipo, lo que implica que no se supone que el usuario sabe ni le importa lo que se esconde detrás del tipo, entonces typedef-ing un tipo de puntero está perfectamente bien. El punto es que hoy podría ser un tipo de puntero, y mañana podría convertirse en un tipo entero, y más tarde podría convertirse en algo más. Este es exactamente el tipo de puntero de typedefs que se usa normalmente en la mayoría de las interfaces de biblioteca.

Usted dice que a veces "no está claro que se ha declarado un puntero". ¡Pero bajo este modelo de uso ese es exactamente el punto! Es supuesto para ser "no claro". El hecho de que el tipo sea un puntero ofuscado no es de su incumbencia. Es algo que no necesita saber y sobre el cual no debe confiar.

Un ejemplo clásico de este modelo de uso es el tipo va_list en la biblioteca estándar. En alguna implementación, podría ser fácilmente un typedef para un tipo de puntero. Pero eso es algo que se supone que no debes saber o en lo que no debes confiar.

Otro ejemplo sería la definición de tipo HWND en Windows API. También es un typedef para el tipo de puntero, pero eso no es asunto tuyo.

  • Una situación completamente diferente es cuando se está typedef-ing un tipo de puntero como una forma de taquigrafía, sólo para hacer las declaraciones más corta para no tener que escribir el carácter * cada vez. En este caso, el hecho de que typedef es (y siempre será) de pie para un tipo de puntero se expone al usuario. Normalmente, este uso no es una buena práctica de programación. Si los usuarios desean crear un alias para evitar escribir * cada vez, pueden hacerlo por sí mismos.

Este modelo de uso generalmente conduce a un código más ofuscado por las razones que ya ha mencionado en su OP.

Ejemplo de este mal uso de typedefs también se puede encontrar en la API de Windows. Los nombres de Typedef como PINT siguen exactamente ese modelo de uso defectuoso.

+0

+1 no podría haberlo dicho mejor –

Cuestiones relacionadas