typedefs y #defines

32

Definitivamente, todos hemos usado typedefsy #defines una u otra vez. Hoy, mientras trabajaba con ellos, comencé a reflexionar sobre algo.

Considere las siguientes 2 situaciones para usar intel tipo de datos con otro nombre:

typedef int MYINTEGER

y

#define MYINTEGER int

Al igual que en la situación anterior, podemos, en muchas situaciones, lograr muy bien algo usando #define, y también hacer lo mismo usando typedef, aunque las formas en que hacemos lo mismo pueden ser bastante diferentes. #define también puede realizar acciones MACRO que un typedef no puede.

Aunque la razón básica para usarlos es diferente, ¿qué tan diferente es su funcionamiento? ¿Cuándo se debe preferir uno sobre el otro cuando se pueden usar ambos? Además, ¿se garantiza que uno sea más rápido que el otro en qué situaciones? (por ejemplo, #define es una directiva de preprocesador, por lo que todo se hace mucho antes que durante la compilación o el tiempo de ejecución).

c0da
fuente
8
Use #define para qué están diseñados. Compilación condicional basada en propiedades que no puede conocer al escribir el código (como OS / Compiler). Usa construcciones de lenguaje de otra manera.
Martin York

Respuestas:

67

Una typedefgeneralmente se prefiere a menos que haya alguna razón extraña que usted necesita específicamente una macro.

Las macros hacen sustitución textual, lo que puede hacer una violencia considerable a la semántica del código. Por ejemplo, dado:

#define MYINTEGER int

podrías escribir legalmente:

short MYINTEGER x = 42;

porque short MYINTEGERse expande a short int.

Por otro lado, con un typedef:

typedef int MYINTEGER:

el nombre MYINTEGERes otro nombre para el tipo int , no una sustitución textual para la palabra clave "int".

Las cosas empeoran aún más con los tipos más complicados. Por ejemplo, dado esto:

typedef char *char_ptr;
char_ptr a, b;
#define CHAR_PTR char*
CHAR_PTR c, d;

a, by cson todos punteros, pero des a char, porque la última línea se expande a:

char* c, d;

que es equivalente a

char *c;
char d;

(Las definiciones de tipo para los tipos de puntero generalmente no son una buena idea, pero esto ilustra el punto).

Otro caso extraño:

#define DWORD long
DWORD double x;     /* Huh? */
Keith Thompson
fuente
1
Punteros para culpar de nuevo! :) ¿Algún otro ejemplo además de los punteros donde no es prudente usar tales macros?
c0da
2
No es que cada vez que haría uso de una macro en lugar de una typedef, pero la forma correcta de construir una macro que hace algo con lo que sigue es el uso de argumentos: #define CHAR_PTR(x) char *x. Esto al menos hará que el compilador se encienda si se usa incorrectamente.
Blrfl
1
No olvides:const CHAR_PTR mutable_pointer_to_const_char; const char_ptr const_pointer_to_mutable_char;
Jon Purdy el
3
Las definiciones también hacen que los mensajes de error sean incomprensibles
Thomas Bonini
Un ejemplo del dolor que puede causar el mal uso de macros (aunque no es directamente relevante para macros vs. typedefs): github.com/Keith-S-Thompson/42
Keith Thompson
15

De lejos, el mayor problema con las macros es que no tienen alcance. Eso solo garantiza el uso de typedef. Además, es más claro semánticamente. Cuando alguien que lee su código ve una definición, no sabrá de qué se trata hasta que lo lea y comprenda toda la macro. Un typedef le dice al lector que se definirá un nombre de tipo. (Debo mencionar que estoy hablando de C ++. No estoy seguro acerca del alcance de typedef para C, pero supongo que es similar).

Tamás Szelei
fuente
Pero como he mostrado en el ejemplo proporcionado en la pregunta, ¡typedef y #define usan la misma cantidad de palabras! Además, estas 3 palabras de una macro no causarán ninguna confusión, ya que las palabras son las mismas, pero solo se reorganizan. :)
c0da
2
Sí, pero no se trata de falta. Una macro puede hacer muchísimas otras cosas además de la definición de tipos. Pero personalmente creo que el alcance es lo más importante.
Tamás Szelei
2
@ c0da: no debe usar macros para typedefs, debe usar typedefs para typedefs. El efecto real de la macro puede ser muy diferente, como lo ilustran varias respuestas.
Joris Timmermans el
15

El código fuente está escrito principalmente para otros desarrolladores. Las computadoras usan una versión compilada.

En esta perspectiva typedeftiene un significado que #defineno tiene.

Mouviciel
fuente
6

La respuesta de Keith Thompson es muy buena y, junto con los puntos adicionales de Tamás Szelei sobre el alcance, deberían proporcionarle todos los antecedentes que necesita.

Siempre debe considerar las macros como el último recurso de los desesperados. Hay ciertas cosas que solo puedes hacer con macros. Incluso entonces, deberías pensar mucho si realmente quieres hacerlo. La cantidad de dolor en la depuración que puede ser causada por macros sutilmente rotas es considerable y lo hará mirar a través de archivos preprocesados. Vale la pena hacerlo solo una vez, para tener una idea del alcance del problema en C ++: solo el tamaño del archivo preprocesado puede ser revelador.

Joris Timmermans
fuente
5

Además de todos los comentarios válidos sobre el alcance y el reemplazo de texto ya dados, #define tampoco es totalmente compatible con typedef! A saber, cuando se trata del caso de los punteros de función.

typedef void(*fptr_t)(void);

Esto declara un tipo fptr_tque es un puntero a una función del tipo void func(void).

No puede declarar este tipo con una macro. #define fptr_t void(*)(void)Obviamente no funcionará. Tendría que escribir algo oscuro como #define fptr_t(name) void(*name)(void), que realmente no tiene ningún sentido en el lenguaje C, donde no tenemos constructores.

Los punteros de matriz tampoco se pueden declarar con #define: typedef int(*arr_ptr)[10];


Y aunque C no tiene soporte de lenguaje para la seguridad de tipos que valga la pena mencionar, otro caso en el que typedef y #define no son compatibles es cuando realiza conversiones de tipos sospechosas. La herramienta de compilación y / o analizador astático podría dar advertencias para tales conversiones si usa typedef.


fuente
1
Aún mejores son las combinaciones de punteros de función y matriz, como char *(*(*foo())[])();( fooes una función que devuelve un puntero a una matriz de punteros a funciones que devuelven el puntero char).
John Bode
4

Use la herramienta con la menor potencia que hace el trabajo y la que tiene más advertencias. #define se evalúa en el preprocesador, en gran medida estás solo allí. typedef es evaluado por el compilador. Se dan verificaciones y typedef solo puede definir tipos, como su nombre lo indica. Entonces, en tu ejemplo, definitivamente ve por typedef.

Martín
fuente
2

Más allá de los argumentos normales en contra de define, ¿cómo escribirías esta función usando Macros?

template <typename IterType>
typename IterType::value_type Sum(
    const IterType& begin, 
    const IterType& end, 
    const IterType::value_type& initialValue)
{
    typename IterType::value_type result = initialValue;
    for (IterType i = begin; i != end; ++i)
        result += i;

    return result;
}

....

vector<int> values;
int sum = Sum(values.begin(), values.end(), 0);

Obviamente, este es un ejemplo trivial, pero esa función puede sumar cualquier secuencia iterable hacia adelante de un tipo que implemente la suma *. Los typedefs utilizados de esta manera son un elemento importante de la programación genérica .

* Acabo de escribir esto aquí, dejo compilarlo como un ejercicio para el lector :-)

EDITAR:

Esta respuesta parece estar causando mucha confusión, así que déjame sacarla más. Si tuviera que mirar dentro de la definición de un vector STL, vería algo similar a lo siguiente:

template <typename ValueType, typename AllocatorType>
class vector
{
public:
    typedef ValueType value_type;
...
}

El uso de typedefs dentro de los contenedores estándar permite que una función genérica (como la que creé anteriormente) haga referencia a estos tipos. La función "Suma" se basa en el tipo de contenedor ( std::vector<int>), no en el tipo contenido dentro del contenedor ( int). Sin typedef no sería posible hacer referencia a ese tipo interno.

Por lo tanto, typedefs es fundamental para Modern C ++ y esto no es posible con Macros.

Chris Pitman
fuente
La pregunta fue sobre macros vs typedef, no macros vs funciones en línea.
Ben Voigt
Claro, y esto solo es posible con typedefs ... eso es value_type.
Chris Pitman
Esto no es un typedef, es una programación de plantilla. Una función o functor no es un tipo, no importa cuán genérico sea.
@Lundin He agregado más detalles: la función Suma solo es posible porque los contenedores estándar usan typedefs para exponer los tipos en los que están diseñados. Estoy no diciendo que la suma es un typedef. Estoy diciendo que std :: vector <T> :: value_type es un typedef. Siéntase libre de ver la fuente de cualquier implementación de biblioteca estándar para verificar.
Chris Pitman el
Lo suficientemente justo. Quizás hubiera sido mejor solo publicar el segundo fragmento.
1

typedefse ajusta a la filosofía de C ++: todas las comprobaciones / afirmaciones posibles en tiempo de compilación. #definees solo un truco de preprocesador que oculta mucha semántica al compilador. No debe preocuparse más por el rendimiento de la compilación que por la corrección del código.

Cuando crea un nuevo tipo, está definiendo una nueva "cosa" que manipula el dominio de su programa. Entonces puede usar esta "cosa" para componer funciones y clases, y puede usar el compilador para su beneficio para hacer comprobaciones estáticas. De todos modos, como C ++ es compatible con C, hay mucha conversión implícita entre inttipos basados ​​en int que no generan advertencias. Por lo tanto, no obtiene toda la potencia de las comprobaciones estáticas en este caso. Sin embargo, puede reemplazar su typedefcon un enumpara encontrar conversiones implícitas. Por ejemplo: si lo has hecho typedef int Age;, puedes reemplazarlo con enum Age { };y obtendrás todo tipo de errores mediante conversiones implícitas entre Agey int.

Otra cosa: el typedefpuede estar dentro de a namespace.

dacap
fuente
1

El uso de define en lugar de typedefs es problemático bajo otro aspecto importante, y es el concepto de rasgos de tipo. Considere diferentes clases (piense en contenedores estándar), todas las cuales definen sus typedefs específicos. Puede escribir código genérico haciendo referencia a los typedefs. Ejemplos de esto incluyen los requisitos generales del contenedor (c ++ estándar 23.2.1 [container.requirements.general]) como

X::value_type
X::reference
X::difference_type
X::size_type

Todo esto no se puede expresar de manera genérica con una macro porque no tiene un alcance.

Francesco
fuente
1

No olvide las implicaciones de esto en su depurador. Algunos depuradores no manejan #define muy bien. Juega con ambos en el depurador que utilizas. Recuerde, pasará más tiempo leyéndolo que escribiéndolo.

luego
fuente
-2

"#define" reemplazará lo que escribió después, typedef creará type. Entonces, si desea tener un tipo personalizado, use typedef. Si necesita macros, use define.

Dainius
fuente
Me encantan las personas que votan negativamente e incluso no se molestan en escribir un comentario.
Dainius