typedefs et #defines

32

Nous avons tous définitivement utilisé typedefs et #defines une fois ou l'autre. Aujourd'hui, en travaillant avec eux, j'ai commencé à réfléchir à quelque chose.

Considérez les 2 situations ci-dessous pour utiliser intle type de données avec un autre nom:

typedef int MYINTEGER

et

#define MYINTEGER int

Comme dans la situation ci-dessus, nous pouvons, dans de nombreuses situations, très bien accomplir une chose en utilisant #define, et faire la même chose en utilisant typedef, bien que les façons dont nous faisons la même chose peuvent être très différentes. #define peut également effectuer des actions MACRO qu'un typedef ne peut pas faire.

Bien que la raison fondamentale de leur utilisation soit différente, dans quelle mesure leur fonctionnement est-il différent? Quand faut-il préférer l'un à l'autre quand les deux peuvent être utilisés? De plus, est-il garanti que l'un sera plus rapide que l'autre dans quelles situations? (par exemple, #define est une directive de préprocesseur, donc tout est fait bien avant la compilation ou l'exécution).

c0da
la source
8
Utilisez #define pour quoi ils sont conçus. Compilation conditionnelle basée sur des propriétés que vous ne pouvez pas connaître lors de l'écriture du code (comme OS / Compiler). Sinon, utilisez des constructions de langage.
Martin York

Réponses:

67

A typedefest généralement préféré, sauf s'il existe une raison étrange pour laquelle vous avez spécifiquement besoin d'une macro.

les macros font une substitution textuelle, ce qui peut faire une violence considérable à la sémantique du code. Par exemple, étant donné:

#define MYINTEGER int

vous pourriez légalement écrire:

short MYINTEGER x = 42;

car se short MYINTEGERdéveloppe à short int.

En revanche, avec un typedef:

typedef int MYINTEGER:

le nom MYINTEGERest un autre nom pour le type int , pas une substitution textuelle pour le mot clé "int".

Les choses empirent encore avec les types plus compliqués. Par exemple, étant donné ceci:

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

a, bEt csont tous les pointeurs, mais dest un char, parce que la dernière ligne se développe à:

char* c, d;

ce qui équivaut à

char *c;
char d;

(Les typedefs pour les types de pointeurs ne sont généralement pas une bonne idée, mais cela illustre le point.)

Un autre cas étrange:

#define DWORD long
DWORD double x;     /* Huh? */
Keith Thompson
la source
1
Pointeurs à blâmer à nouveau! :) Un autre exemple que des pointeurs où il n'est pas judicieux d'utiliser de telles macros?
c0da
2
Non pas que je jamais utiliser une macro en place d'un typedef, mais la façon de construire une macro qui fait quelque chose avec tout ce qui suit est d'utiliser des arguments: #define CHAR_PTR(x) char *x. Cela entraînera au moins le compilateur à kvetch s'il n'est pas utilisé correctement.
Blrfl
1
N'oubliez pas:const CHAR_PTR mutable_pointer_to_const_char; const char_ptr const_pointer_to_mutable_char;
Jon Purdy
3
Définit également rendre les messages d'erreur incompréhensibles
Thomas Bonini
Un exemple de la douleur qu'une mauvaise utilisation des macros peut causer (bien que cela ne concerne pas directement les macros et les typedefs): github.com/Keith-S-Thompson/42
Keith Thompson
15

De loin, le plus gros problème avec les macros est qu'elles ne sont pas étendues. Cela seul justifie l'utilisation de typedef. De plus, c'est plus clair sémantiquement. Quand quelqu'un qui lit votre code voit une définition, il ne saura pas de quoi il s'agit tant qu'il ne l'a pas lu et ne comprend pas la macro entière. Un typedef indique au lecteur qu'un nom de type sera défini. (Je dois mentionner que je parle de C ++. Je ne suis pas sûr de la portée de typedef pour C, mais je suppose que c'est similaire).

Tamás Szelei
la source
Mais comme je l'ai montré dans l'exemple fourni dans la question, un typedef et #define utilisent le même nombre de mots! De plus, ces 3 mots d'une macro ne causeront aucune confusion, car les mots sont les mêmes, mais juste réarrangés. :)
c0da
2
Oui, mais il ne s'agit pas de brièveté. Une macro peut faire des millions d'autres choses en plus de la dactylographie. Mais personnellement, je pense que le cadrage est le plus important.
Tamás Szelei
2
@ c0da - vous ne devez pas utiliser de macros pour les typedefs, vous devez utiliser les typedefs pour les typedefs. L'effet réel de la macro peut être très différent, comme l'illustrent différentes réponses.
Joris Timmermans
15

Le code source est principalement écrit pour les autres développeurs. Les ordinateurs en utilisent une version compilée.

Dans cette perspective, il a typedefun sens qui #definene l'est pas.

mouviciel
la source
6

La réponse de Keith Thompson est très bonne et avec les points supplémentaires de Tamás Szelei sur la portée, devrait fournir tout le fond dont vous avez besoin.

Vous devez toujours considérer les macros comme le tout dernier recours des désespérés. Il y a certaines choses que vous ne pouvez faire qu'avec des macros. Même alors, vous devriez réfléchir longuement et sérieusement si vous voulez vraiment le faire. La quantité de douleur dans le débogage qui peut être causée par des macros subtilement brisées est considérable et vous obligera à parcourir les fichiers prétraités. Cela vaut la peine de le faire une seule fois, pour avoir une idée de l'étendue du problème en C ++ - la taille du fichier prétraité peut être révélatrice.

Joris Timmermans
la source
5

En plus de toutes les remarques valables sur la portée et le remplacement de texte déjà données, #define n'est pas non plus entièrement compatible avec typedef! À savoir lorsqu'il s'agit du cas des pointeurs de fonction.

typedef void(*fptr_t)(void);

Cela déclare un type fptr_tqui est un pointeur vers une fonction du type void func(void).

Vous ne pouvez pas déclarer ce type avec une macro. #define fptr_t void(*)(void)ne fonctionnera évidemment pas. Il faudrait écrire quelque chose d'obscur comme #define fptr_t(name) void(*name)(void), ce qui n'a vraiment aucun sens dans le langage C, où nous n'avons pas de constructeurs.

Les pointeurs de tableau ne peuvent pas non plus être déclarés avec #define: typedef int(*arr_ptr)[10];


Et bien que C n'ait pas de prise en charge linguistique pour la sécurité des types à noter, un autre cas où typedef et #define ne sont pas compatibles est lorsque vous effectuez des conversions de types suspectes. Le compilateur et / ou l'outil d'analyseur astatique peuvent être en mesure de donner des avertissements pour de telles conversions si vous utilisez typedef.


la source
1
Encore mieux sont les combinaisons de pointeurs de fonction et de tableau, comme char *(*(*foo())[])();( fooest une fonction renvoyant un pointeur vers un tableau de pointeurs vers des fonctions renvoyant le pointeur char).
John Bode
4

Utilisez l'outil avec le moins de puissance pour faire le travail et celui avec le plus d'avertissements. #define est évalué dans le préprocesseur, vous y êtes largement seul. typedef est évalué par le compilateur. Des vérifications sont données et typedef ne peut définir que des types, comme son nom l'indique. Donc, dans votre exemple, choisissez définitivement typedef.

Martin
la source
2

Au-delà des arguments normaux contre define, comment écririez-vous cette fonction à l'aide de 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);

C'est évidemment un exemple trivial, mais cette fonction peut résumer n'importe quelle séquence itérative vers l'avant d'un type qui implémente l'addition *. Les typedefs utilisés comme celui-ci sont un élément important de la programmation générique .

* Je viens d'écrire ceci ici, je laisse le compiler comme un exercice pour le lecteur :-)

MODIFIER:

Cette réponse semble être source de beaucoup de confusion, alors permettez-moi de la développer davantage. Si vous regardiez à l'intérieur de la définition d'un vecteur STL, vous verriez quelque chose de similaire à ce qui suit:

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

L'utilisation de typedefs dans les conteneurs standard permet à une fonction générique (comme celle que j'ai créée ci-dessus) de référencer ces types. La fonction "Sum" est basée sur le type du conteneur ( std::vector<int>), pas sur le type contenu à l'intérieur du conteneur ( int). Sans typedef, il ne serait pas possible de se référer à ce type interne.

Par conséquent, les typedefs sont au cœur de Modern C ++ et cela n'est pas possible avec les macros.

Chris Pitman
la source
La question portait sur les macros vs typedef, et non sur les fonctions macros vs inline.
Ben Voigt
Bien sûr, et cela n'est possible qu'avec des typedefs ... c'est ce qu'est value_type.
Chris Pitman
Ce n'est pas un typedef, c'est de la programmation de modèles. Une fonction ou un foncteur n'est pas un type, aussi générique soit-il.
@Lundin J'ai ajouté plus de détails: La fonction Sum n'est possible que parce que les conteneurs standard utilisent des typedefs pour exposer les types sur lesquels ils sont basés sur des modèles. Je ne dis pas que Sum est un typedef. Je dis que std :: vector <T> :: value_type est un typedef. N'hésitez pas à regarder la source de toute implémentation de bibliothèque standard pour vérifier.
Chris Pitman
C'est suffisant. Il aurait peut-être été préférable de ne publier que le deuxième extrait.
1

typedefcorrespond à la philosophie C ++: toutes les vérifications / assertions possibles en temps de compilation. #defineest juste une astuce de préprocesseur qui cache beaucoup de sémantique au compilateur. Vous ne devriez pas vous soucier des performances de compilation plus que de l'exactitude du code.

Lorsque vous créez un nouveau type, vous définissez une nouvelle «chose» que le domaine de votre programme manipule. Vous pouvez donc utiliser cette «chose» pour composer des fonctions et des classes, et vous pouvez utiliser le compilateur à votre avantage pour effectuer des vérifications statiques. Quoi qu'il en soit, comme C ++ est compatible avec C, il existe de nombreuses conversions implicites entre les inttypes basés sur et int qui ne génèrent pas d'avertissements. Ainsi, vous n'obtenez pas la pleine puissance des vérifications statiques dans ce cas. Cependant, vous pouvez remplacer votre typedefpar un enumpour rechercher des conversions implicites. Par exemple: si vous l'avez fait typedef int Age;, vous pouvez le remplacer par enum Age { };et vous obtiendrez toutes sortes d'erreurs par des conversions implicites entre Ageet int.

Une autre chose: le typedefpeut être à l'intérieur d'un namespace.

dacap
la source
1

L'utilisation de définitions au lieu de typedefs est troublante sous un autre aspect important, à savoir le concept de traits de type. Considérez différentes classes (pensez aux conteneurs standard) qui définissent toutes leurs typedefs spécifiques. Vous pouvez écrire du code générique en vous référant aux typedefs. Des exemples de cela incluent les exigences générales du conteneur (norme c ++ 23.2.1 [container.requirements.general]) comme

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

Tout cela n'est pas exprimable de manière générique avec une macro car il n'est pas délimité.

Francesco
la source
1

N'oubliez pas les implications de cela dans votre débogueur. Certains débogueurs ne gèrent pas très bien #define. Jouez avec les deux dans le débogueur que vous utilisez. N'oubliez pas que vous passerez plus de temps à le lire qu'à l'écrire.

anon
la source
-2

"#define" remplacera ce que vous avez écrit après, typedef créera du type. Donc, si vous voulez avoir un type personnalisé, utilisez typedef. Si vous avez besoin de macros - utilisez define.

Dainius
la source
J'adore les gens qui votent et qui ne prennent même pas la peine d'écrire un commentaire.
Dainius