typedefs und #defines

32

Wir alle haben definitiv das eine oder andere Mal typedefs und #defines benutzt. Während ich heute mit ihnen zusammenarbeite, habe ich angefangen, über etwas nachzudenken.

Betrachten Sie die folgenden 2 Situationen, um einen intDatentyp mit einem anderen Namen zu verwenden:

typedef int MYINTEGER

und

#define MYINTEGER int

Wie in der obigen Situation können wir in vielen Situationen mit #define sehr gut etwas erreichen und auch mit typedef das Gleiche tun, obwohl die Art und Weise, wie wir dasselbe tun, sehr unterschiedlich sein kann. #define kann auch MACRO-Aktionen ausführen, die ein typedef nicht ausführen kann.

Obwohl der Hauptgrund für ihre Verwendung der unterschiedliche ist, wie unterschiedlich ist ihre Arbeitsweise? Wann sollte einer dem anderen vorgezogen werden, wenn beide verwendet werden können? Auch ist einer in welchen Situationen garantiert schneller als der andere? (zB #define ist eine Präprozessor-Direktive, so dass alles viel früher als beim Kompilieren oder zur Laufzeit ausgeführt wird).

c0da
quelle
8
Verwenden Sie #definieren, wofür sie entwickelt wurden. Bedingte Kompilierung basierend auf Eigenschaften, die Sie beim Schreiben des Codes nicht kennen (z. B. OS / Compiler). Verwenden Sie ansonsten Sprachkonstrukte.
Martin York

Antworten:

67

A typedefwird im Allgemeinen bevorzugt, es sei denn, es gibt einen merkwürdigen Grund, warum Sie speziell ein Makro benötigen.

Makros ersetzen den Text, was der Semantik des Codes erheblichen Schaden zufügen kann. Zum Beispiel gegeben:

#define MYINTEGER int

Sie könnten legal schreiben:

short MYINTEGER x = 42;

weil short MYINTEGERerweitert sich um short int.

Auf der anderen Seite mit einem typedef:

typedef int MYINTEGER:

Der Name MYINTEGERist ein anderer Name für den Typ int und keine textuelle Ersetzung für das Schlüsselwort "int".

Bei komplizierteren Typen wird es noch schlimmer. Zum Beispiel vorausgesetzt, dass:

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

a, bUnd csind alle Zeiger, sondern dein char, weil die letzte Zeile erweitert:

char* c, d;

das ist äquivalent zu

char *c;
char d;

(Typedefs für Zeigertypen sind normalerweise keine gute Idee, aber dies veranschaulicht den Punkt.)

Ein weiterer merkwürdiger Fall:

#define DWORD long
DWORD double x;     /* Huh? */
Keith Thompson
quelle
1
Zeiger wieder schuld! :) Gibt es außer Zeigern noch ein anderes Beispiel, bei dem es unklug ist, solche Makros zu verwenden?
c0da
2
Nicht , dass ich jemals ein Makro anstelle eines verwenden würde typedef, aber der richtige Weg , ein Makro zu erstellen , die etwas tut , mit dem, was folgt , ist Argumente zu verwenden: #define CHAR_PTR(x) char *x. Dies führt zumindest dazu, dass der Compiler bei falscher Verwendung kvetch ausführt.
Blrfl
1
Vergessen Sie nicht:const CHAR_PTR mutable_pointer_to_const_char; const char_ptr const_pointer_to_mutable_char;
Jon Purdy
3
Definiert machen auch Fehlermeldungen unverständlich
Thomas Bonini
Ein Beispiel für den Schmerz, den der Missbrauch von Makros verursachen kann (obwohl nicht direkt relevant für Makros vs. typedefs): github.com/Keith-S-Thompson/42
Keith Thompson
15

Das mit Abstand größte Problem bei Makros ist, dass sie nicht über einen bestimmten Bereich verfügen. Dies allein rechtfertigt die Verwendung von typedef. Auch ist es semantisch klarer. Wenn jemand, der Ihren Code liest, eine Definition sieht, weiß er erst, worum es geht, wenn er sie liest und das gesamte Makro versteht. Ein typedef teilt dem Leser mit, dass ein Typenname definiert wird. (Ich sollte erwähnen, dass ich über C ++ spreche. Ich bin nicht sicher, ob Typedef für C geeignet ist, aber ich denke, es ist ähnlich).

Tamás Szelei
quelle
Aber wie ich im Beispiel in der Frage gezeigt habe, verwenden ein typedef und #define die gleiche Anzahl von Wörtern! Auch diese 3 Wörter eines Makros verursachen keine Verwirrung, da die Wörter gleich sind, sondern nur neu angeordnet werden. :)
c0da
2
Ja, aber es geht nicht um Kürze. Ein Makro kann neben der Typendefinition noch eine Unmenge anderer Aufgaben ausführen. Aber ich persönlich halte Scoping für das Wichtigste.
Tamás Szelei
2
@ c0da - Sie sollten keine Makros für Typedefs verwenden, Sie sollten Typedefs für Typedefs verwenden. Die tatsächliche Wirkung des Makros kann sehr unterschiedlich sein, wie verschiedene Reaktionen zeigen.
Joris Timmermans
15

Der Quellcode ist hauptsächlich für Mitentwickler geschrieben. Computer verwenden eine kompilierte Version davon.

In dieser Perspektive typedefträgt eine Bedeutung, #definedie nicht.

mouviciel
quelle
6

Die Antwort von Keith Thompson ist sehr gut und sollte zusammen mit den zusätzlichen Punkten von Tamás Szelei zum Scoping den gesamten Hintergrund liefern, den Sie benötigen.

Sie sollten Makros immer als das allerletzte Mittel der Verzweiflung betrachten. Es gibt bestimmte Dinge, die Sie nur mit Makros tun können. Selbst dann sollten Sie lange und gründlich nachdenken, wenn Sie es wirklich wollen. Die Menge an Problemen beim Debuggen, die durch subtil defekte Makros verursacht werden können, ist beträchtlich und lässt Sie vorverarbeitete Dateien durchsehen. Es lohnt sich, dies nur einmal zu tun, um ein Gefühl für den Umfang des Problems in C ++ zu bekommen - allein die Größe der vorverarbeiteten Datei kann ein Augenöffner sein.

Joris Timmermans
quelle
5

Zusätzlich zu allen gültigen Hinweisen zu Umfang und Textersetzung, die bereits gegeben wurden, ist #define auch nicht vollständig kompatibel mit typedef! Nämlich, wenn es um Funktionszeiger geht.

typedef void(*fptr_t)(void);

Dies deklariert einen Typ, fptr_tder ein Zeiger auf eine Funktion des Typs ist void func(void).

Sie können diesen Typ nicht mit einem Makro deklarieren. #define fptr_t void(*)(void)wird natürlich nicht funktionieren. Man müsste so etwas Obskures schreiben #define fptr_t(name) void(*name)(void), was in der C-Sprache, in der wir keine Konstruktoren haben, wirklich keinen Sinn ergibt.

Array-Zeiger können auch nicht mit #define deklariert werden: typedef int(*arr_ptr)[10];


Und während C keine nennenswerte Sprachunterstützung für die Typensicherheit bietet, ist ein anderer Fall, in dem typedef und #define nicht kompatibel sind, der, wenn Sie verdächtige Typenkonvertierungen durchführen. Der Compiler und / oder das Astatic Analyzer-Tool können möglicherweise Warnungen für solche Konvertierungen ausgeben, wenn Sie typedef verwenden.


quelle
1
Noch besser sind Kombinationen von Funktions- und Array-Zeigern wie char *(*(*foo())[])();( fooist eine Funktion, die einen Zeiger auf ein Array von Zeigern auf Funktionen zurückgibt, auf die der Zeiger zurückgegeben wird char).
John Bode
4

Verwenden Sie das Werkzeug mit der geringsten Leistung und den meisten Warnungen. #define wird im Präprozessor ausgewertet, Sie sind dort weitgehend alleine. typedef wird vom Compiler ausgewertet. Es werden Überprüfungen durchgeführt und typedef kann nur Typen definieren, wie der Name sagt. Entscheide dich also in deinem Beispiel definitiv für typedef.

Martin
quelle
2

Wie würden Sie, abgesehen von den normalen Argumenten gegen define, diese Funktion mit Makros schreiben?

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

Dies ist offensichtlich ein triviales Beispiel, aber diese Funktion kann über jede vorwärts iterierbare Sequenz eines Typs summiert werden, der die Addition * implementiert. Solche Typedefs sind ein wichtiges Element der generischen Programmierung .

* Ich habe das gerade hier geschrieben, ich lasse es als Übung für den Leser zusammenstellen :-)

BEARBEITEN:

Diese Antwort scheint viel Verwirrung zu stiften. Wenn Sie in die Definition eines STL-Vektors schauen, sehen Sie etwas Ähnliches wie das Folgende:

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

Die Verwendung von typedefs in den Standardcontainern ermöglicht es einer generischen Funktion (wie der oben erstellten), auf diese Typen zu verweisen. Die Funktion "Summe" richtet sich nach dem Typ des Containers ( std::vector<int>) und nicht nach dem Typ, der sich im Container befindet ( int). Ohne typedef wäre es nicht möglich, auf diesen internen Typ zu verweisen.

Daher sind typedefs von zentraler Bedeutung für Modern C ++, und dies ist mit Makros nicht möglich.

Chris Pitman
quelle
Die Frage stellte sich zu Makros vs typedef, nicht zu Makros vs Inline-Funktionen.
Ben Voigt
Klar, und das ist nur mit typedefs möglich ... das ist value_type.
Chris Pitman
Dies ist keine typedef, es ist Template-Programmierung. Eine Funktion oder ein Funktor ist kein Typ, egal wie allgemein er ist.
@Lundin Ich habe weitere Details hinzugefügt: Die Funktion "Summe" ist nur möglich, weil Standardcontainer typedefs verwenden, um die Typen anzuzeigen, für die sie Vorlagen verwenden. Ich sage nicht , dass Sum ein typedef ist. Ich sage std :: vector <T> :: value_type ist ein typedef. Sie können sich jederzeit die Quelle einer Standardbibliotheksimplementierung ansehen, um diese zu überprüfen.
Chris Pitman
Meinetwegen. Es wäre vielleicht besser gewesen, nur das zweite Snippet zu posten.
1

typedefpasst zur C ++ Philosophie: alle möglichen Checks / Asserts in der Kompilierungszeit. #defineist nur ein Präprozessor-Trick, der dem Compiler viel Semantik verbirgt. Sie sollten sich nicht mehr um die Kompilierungsleistung als um die Code-Korrektheit kümmern.

Wenn Sie einen neuen Typ erstellen, definieren Sie ein neues "Ding", das die Domäne Ihres Programms manipuliert. Sie können dieses "Ding" also verwenden, um Funktionen und Klassen zu komponieren, und Sie können den Compiler zu Ihrem Vorteil verwenden, um statische Überprüfungen durchzuführen. Wie auch immer, da C ++ mit C kompatibel ist, gibt es eine Menge impliziter Konvertierungen zwischen intund int-basierten Typen, die keine Warnungen generieren. In diesem Fall erhalten Sie nicht die volle Leistung der statischen Überprüfungen. Sie können jedoch Ihre typedefdurch eine ersetzen enum, um implizite Konvertierungen zu finden. ZB: Wenn Sie haben typedef int Age;, können Sie durch ersetzen, enum Age { };und Sie erhalten alle Arten von Fehlern durch implizite Konvertierungen zwischen Ageund int.

Eine andere Sache: die typedefkann in einem sein namespace.

dacap
quelle
1

Die Verwendung von Defines anstelle von typedefs ist unter einem anderen wichtigen Aspekt problematisch, nämlich dem Konzept der Typmerkmale. Betrachten Sie verschiedene Klassen (denken Sie an Standardcontainer), die alle ihre spezifischen Typendefinitionen definieren. Sie können generischen Code schreiben, indem Sie auf die typedefs verweisen. Beispiele hierfür sind die allgemeinen Containeranforderungen (c ++ Standard 23.2.1 [container.requirements.general]) wie

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

All dies lässt sich mit einem Makro nicht generisch ausdrücken, da es keinen Gültigkeitsbereich hat.

Francesco
quelle
1

Vergessen Sie nicht, welche Auswirkungen dies auf Ihren Debugger hat. Einige Debugger behandeln #define nicht sehr gut. Spielen Sie mit beiden in dem von Ihnen verwendeten Debugger. Denken Sie daran, Sie werden mehr Zeit damit verbringen, es zu lesen als zu schreiben.

anon
quelle
-2

"#define" ersetzt das, was Sie geschrieben haben, typedef erstellt den Typ. Wenn Sie also einen benutzerdefinierten Typ haben möchten, verwenden Sie typedef. Wenn Sie Makros benötigen, verwenden Sie define.

Dainius
quelle
Ich liebe Leute, die abstimmen und sich nicht einmal die Mühe machen, einen Kommentar zu schreiben.
Dainius