web-dev-qa-db-fra.com

typedefs et #defines

Nous avons tous certainement 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 le type de données int 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).

32
c0da

Un typedef est 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;

parce que short MYINTEGER se développe en short int.

En revanche, avec un typedef:

typedef int MYINTEGER:

le nom MYINTEGER est un autre nom pour le typeint, 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, b et c sont tous des pointeurs, mais d est un char, car la dernière ligne se développe en:

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? */
67
Keith Thompson

De loin, le plus gros problème avec les macros est qu'elles ne sont pas étendues. Cela seul justifie l'utilisation de typedef. En outre, 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 jusqu'à ce qu'il la lise et comprenne 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).

15
Tamás Szelei

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

Dans cette perspective, typedef signifie que #define non.

15
mouviciel

réponse de Keith Thompson est très bon et avec 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.

6
Joris Timmermans

En plus de toutes les remarques valables sur l'étendue 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_t Qui est un pointeur vers une fonction de 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), 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 aucune prise en charge linguistique pour la sécurité des types digne d'être mentionné, 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'analyse astatique peuvent être en mesure de donner des avertissements pour de telles conversions si vous utilisez typedef.

5
user29079

Utilisez l'outil avec le moins de puissance qui fait 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.

4
Martin

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 toute séquence itérable directe d'un type qui implémente l'addition *. Les typedefs utilisés comme ceci sont un élément important de Programmation générique .

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

ÉDITER:

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 au suivant:

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 dans le 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.

2
Chris Pitman

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.

1
anon

typedef correspond à la philosophie C++: toutes les vérifications/assertions possibles en temps de compilation. #define n'est qu'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 y a beaucoup de conversion implicite entre int et les types basés sur 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 typedef par un enum pour rechercher des conversions implicites. Par exemple: si vous avez typedef int Age;, vous pouvez alors remplacer par enum Age { }; et vous obtiendrez toutes sortes d'erreurs par des conversions implicites entre Age et int.

Autre chose: le typedef peut être à l'intérieur d'un namespace.

1
dacap

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é.

1
Francesco