typedefs e #defines


32

Abbiamo sicuramente usato typedefs e #defines una volta o l'altra. Oggi, mentre lavoravo con loro, ho iniziato a meditare su una cosa.

Considera le 2 situazioni seguenti per utilizzare il inttipo di dati con un altro nome:

typedef int MYINTEGER

e

#define MYINTEGER int

Come sopra, in molte situazioni possiamo fare molto bene qualcosa usando #define, e fare lo stesso usando typedef, anche se i modi in cui facciamo lo stesso possono essere abbastanza diversi. #define può anche eseguire azioni MACRO che un typedef non può.

Sebbene il motivo di base per usarli sia diverso, quanto è diverso il loro funzionamento? Quando uno dovrebbe essere preferito rispetto all'altro quando entrambi possono essere utilizzati? Inoltre, uno è garantito per essere più veloce dell'altro in quali situazioni? (ad es. #define è la direttiva del preprocessore, quindi tutto viene eseguito molto prima rispetto alla compilazione o al runtime).


8
Usa #define per cosa sono progettati. Compilazione condizionale basata su proprietà che non è possibile conoscere quando si scrive il codice (come OS / Compiler). Utilizzare altrimenti i costrutti del linguaggio.
Martin York,

Risposte:


67

A typedefè generalmente preferito a meno che non ci sia qualche strana ragione per cui hai specificamente bisogno di una macro.

le macro effettuano una sostituzione testuale, che può causare una notevole violenza alla semantica del codice. Ad esempio, dato:

#define MYINTEGER int

potresti legalmente scrivere:

short MYINTEGER x = 42;

perché si short MYINTEGERespande a short int.

D'altra parte, con un typedef:

typedef int MYINTEGER:

il nome MYINTEGERè un altro nome per il tipo int , non una sostituzione testuale per la parola chiave "int".

Le cose peggiorano ancora con tipi più complicati. Ad esempio, dato questo:

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

a, bE csono tutti i puntatori, ma dè una char, perché l'ultima riga si espande a:

char* c, d;

che equivale a

char *c;
char d;

(I typedef per i tipi di puntatore di solito non sono una buona idea, ma questo illustra il punto.)

Un altro caso strano:

#define DWORD long
DWORD double x;     /* Huh? */

1
Puntatori di nuovo da incolpare! :) Qualche altro esempio oltre ai puntatori in cui non è saggio usare tali macro?
c0da

2
Non che avessi mai utilizzare una macro al posto di una typedef, ma il modo corretto per costruire una macro che fa qualcosa con tutto ciò che segue è quello di utilizzare argomenti: #define CHAR_PTR(x) char *x. Ciò provocherà almeno il kvetch del compilatore se usato in modo errato.
Blrfl,

1
Non dimenticare:const CHAR_PTR mutable_pointer_to_const_char; const char_ptr const_pointer_to_mutable_char;
Jon Purdy,

3
Definisce anche i messaggi di errore incomprensibili
Thomas Bonini,

Un esempio del dolore che può causare un uso improprio delle macro (anche se non direttamente rilevante per le macro rispetto ai typedef): github.com/Keith-S-Tompson/42
Keith Thompson

15

Di gran lunga, il problema più grande con le macro è che non hanno ambito. Questo da solo garantisce l'uso di typedef. Inoltre, è più chiaro semanticamente. Quando qualcuno che legge il tuo codice vede una definizione, non saprà di cosa si tratta fino a quando non lo legge e comprende l'intera macro. Un typedef dice al lettore che verrà definito un nome di tipo. (Dovrei menzionare che sto parlando di C ++. Non sono sicuro dell'ambito tipedef per C, ma immagino che sia simile).


Ma come ho mostrato nell'esempio fornito nella domanda, un typedef e #define usano lo stesso numero di parole! Inoltre, queste 3 parole di una macro non causeranno confusione, poiché le parole sono le stesse, ma semplicemente riorganizzate. :)
c0da

2
Sì, ma non si tratta di mancanza. Una macro può fare molte altre cose oltre a scrivere a macchina. Ma personalmente penso che l'ambito sia il più importante.
Tamás Szelei,

2
@ c0da - non dovresti usare le macro per i typedef, dovresti usare i typedefs per i typedef. L'effetto reale della macro può essere molto diverso, come illustrato da varie risposte.
Joris Timmermans,

15

Il codice sorgente è scritto principalmente per i colleghi sviluppatori. I computer ne usano una versione compilata.

In questa prospettiva typedefha un significato che #definenon lo è.


6

La risposta di Keith Thompson è ottima e insieme punti aggiuntivi di Tamás Szelei sullo scoping dovrebbe fornire tutto lo sfondo di cui hai bisogno.

Dovresti sempre considerare le macro come l'ultima risorsa del disperato. Ci sono alcune cose che puoi fare solo con le macro. Anche allora, dovresti pensare a lungo e intensamente se vuoi davvero farlo. La quantità di dolore nel debug che può essere causata da macro sottilmente spezzate è considerevole e ti farà guardare attraverso i file preelaborati. Vale la pena farlo una sola volta, per avere un'idea della portata del problema in C ++: solo la dimensione del file preelaborato potrebbe aprire gli occhi.


5

Oltre a tutte le osservazioni valide sull'ambito e sulla sostituzione del testo già fornite, #define non è completamente compatibile con typedef! Vale a dire quando si tratta del caso dei puntatori a funzione.

typedef void(*fptr_t)(void);

Questo dichiara un tipo fptr_t che è un puntatore a una funzione del tipo void func(void).

Non è possibile dichiarare questo tipo con una macro. #define fptr_t void(*)(void)ovviamente non funzionerà. Dovresti scrivere qualcosa di oscuro #define fptr_t(name) void(*name)(void), che in realtà non ha alcun senso nel linguaggio C, dove non abbiamo costruttori.

I puntatori di matrice non possono essere dichiarati con #define: typedef int(*arr_ptr)[10];


E mentre C non ha alcun supporto linguistico per la sicurezza dei tipi degno di nota, un altro caso in cui typedef e #define non sono compatibili è quando si eseguono conversioni di tipo sospette. Lo strumento compilatore e / o analizzatore astatico potrebbe essere in grado di fornire avvisi per tali conversioni se si utilizza typedef.


1
Ancora meglio sono le combinazioni di puntatori di funzioni e array, come char *(*(*foo())[])();( fooè una funzione che restituisce un puntatore a un array di puntatori a funzioni che restituiscono il puntatore a char).
John Bode,

4

Utilizzare lo strumento con la minima potenza che esegue il lavoro e quello con la maggior parte degli avvisi. #define viene valutato nel preprocessore, ci si trova in gran parte lì da soli. typedef è valutato dal compilatore. I controlli sono dati e typedef può solo definire i tipi, come dice il nome. Quindi, nel tuo esempio, scegli sicuramente typedef.


2

Al di là dei normali argomenti contro define, come scriveresti questa funzione usando i Macro?

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);

Questo è ovviamente un esempio banale, ma quella funzione può riassumere qualsiasi sequenza iterabile in avanti di un tipo che implementa l'addizione *. I typedef usati in questo modo sono un elemento importante della programmazione generica .

* Ho appena scritto questo qui, lascio compilarlo come un esercizio per il lettore :-)

MODIFICARE:

Questa risposta sembra essere causa di molta confusione, quindi permettetemi di trarne di più. Se dovessi guardare all'interno della definizione di un vettore STL, vedresti qualcosa di simile al seguente:

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

L'uso di typedef all'interno dei contenitori standard consente a una funzione generica (come quella che ho creato sopra) di fare riferimento a questi tipi. La funzione "Somma" è basata sul tipo di contenitore ( std::vector<int>), non sul tipo contenuto nel contenitore (int ). Senza typedef non sarebbe possibile fare riferimento a quel tipo interno.

Pertanto, i typedef sono fondamentali per il C ++ moderno e questo non è possibile con i macro.


La domanda posta sulle typedeffunzioni macro vs , non macro vs inline.
Ben Voigt,

Certo, e questo è possibile solo con typedefs ... questo è value_type.
Chris Pitman,

Questo non è un typedef, è una programmazione template. Una funzione o un funzione non è un tipo, non importa quanto sia generico.

@Lundin Ho aggiunto ulteriori dettagli: La funzione Sum è possibile solo perché i contenitori standard usano typedefs per esporre i tipi su cui sono modellati. Sto Non dicendo che Sum è un typedef. Sto dicendo std :: vector <T> :: value_type è un typedef. Sentiti libero di controllare l'origine di qualsiasi implementazione di libreria standard per verificare.
Chris Pitman,

Giusto. Forse sarebbe stato meglio pubblicare solo il secondo frammento.

1

typedef si adatta alla filosofia C ++: tutti i possibili controlli / asserzioni in fase di compilazione. #defineè solo un trucco del preprocessore che nasconde molto semantico al compilatore. Non dovresti preoccuparti delle prestazioni della compilazione più della correttezza del codice.

Quando crei un nuovo tipo, stai definendo una nuova "cosa" che il dominio del tuo programma manipola. Quindi puoi usare questa "cosa" per comporre funzioni e classi e puoi usare il compilatore a tuo vantaggio per fare controlli statici. Ad ogni modo, poiché C ++ è compatibile con C, ci sono molte conversioni implicite tra inttipi basati su int che non generano avvisi. In questo caso, quindi, non si ottiene la piena potenza dei controlli statici. Tuttavia, potresti sostituire il tuo typedefcon un enumper trovare conversioni implicite. Ad esempio: se lo hai fatto typedef int Age;, puoi sostituirlo con enum Age { };e otterrai tutti i tipi di errori da conversioni implicite tra Agee int.

Un'altra cosa: typedefpuò essere dentro a namespace.


1

L'uso di definisce invece di typedefs è preoccupante sotto un altro aspetto importante, e cioè il concetto di tratti di tipo. Prendi in considerazione classi diverse (pensa a contenitori standard) che definiscono tutti i loro specifici typedef. Puoi scrivere codice generico facendo riferimento ai typedef. Esempi di questo includono i requisiti generali del contenitore (standard c ++ 23.2.1 [container.requirements.general]) come

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

Tutto ciò non è espressibile in modo generico con una macro perché non ha ambito.


1

Non dimenticare le implicazioni di questo nel tuo debugger. Alcuni debugger non gestiscono molto bene #define. Gioca con entrambi nel debugger che usi. Ricorda, passerai più tempo a leggerlo che a scriverlo.


-2

"#define" sostituirà ciò che hai scritto dopo, typedef creerà il tipo. Quindi, se vuoi avere un tipo personalizzato, usa typedef. Se hai bisogno di macro, usa define.


Adoro le persone che non votano e non si preoccupano nemmeno di scrivere un commento.
Dainius,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.