typedefs i #defines

32

Wszyscy zdecydowanie używaliśmy typedefsi #defineraz lub drugi. Dzisiaj, pracując z nimi, zacząłem się nad czymś zastanawiać.

Rozważ poniższe 2 sytuacje, aby użyć inttypu danych o innej nazwie:

typedef int MYINTEGER

i

#define MYINTEGER int

Podobnie jak w powyższej sytuacji, w wielu sytuacjach możemy bardzo dobrze osiągnąć coś za pomocą #define, a także zrobić to samo za pomocą typedef, chociaż sposoby, w jakie to robimy, mogą być zupełnie inne. #define może także wykonywać akcje MAKRO, których typedef nie może.

Chociaż podstawowy powód ich używania jest inny, to jak różni się ich działanie? Kiedy należy preferować jedno z nich, gdy można używać obu? Ponadto, czy jedna z nich jest szybsza niż druga w jakich sytuacjach? (np. #define jest dyrektywą preprocesora, więc wszystko odbywa się znacznie wcześniej niż podczas kompilacji lub uruchamiania).

c0da
źródło
8
Użyj #define, do czego są przeznaczone. Kompilacja warunkowa oparta na właściwościach, których nie można znać podczas pisania kodu (np. OS / Compiler). W przeciwnym razie używaj konstrukcji językowych.
Martin York,

Odpowiedzi:

67

A typedefjest ogólnie preferowane, chyba że istnieje jakiś dziwny powód, dla którego potrzebujesz makro.

makra dokonują podstawienia tekstowego, co może znacznie naruszać semantykę kodu. Na przykład biorąc pod uwagę:

#define MYINTEGER int

możesz legalnie napisać:

short MYINTEGER x = 42;

ponieważ short MYINTEGERrozwija się do short int.

Z drugiej strony z typedef:

typedef int MYINTEGER:

nazwa MYINTEGERjest inną nazwą typu int , a nie tekstowym zamiennikiem słowa kluczowego „int”.

Gorzej jest z bardziej skomplikowanymi typami. Na przykład biorąc pod uwagę:

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

a, bI cto wszystkie wskaźniki, ale dto char, ponieważ ostatni wiersz rozszerza się do:

char* c, d;

co jest równoważne z

char *c;
char d;

(Typedefs dla typów wskaźników zwykle nie jest dobrym pomysłem, ale ilustruje to sens).

Kolejny dziwny przypadek:

#define DWORD long
DWORD double x;     /* Huh? */
Keith Thompson
źródło
1
Wskaźniki do winy ponownie! :) Jakikolwiek inny przykład poza wskaźnikami, w których nierozsądne jest używanie takich makr?
c0da
2
Nie żebym kiedykolwiek użyć makra w miejsce typedef, ale właściwego sposobu budowania makro, które robi coś z tym, co następuje, jest w użyciu argumentów: #define CHAR_PTR(x) char *x. Spowoduje to co najmniej spowodowanie nieprawidłowego użycia kompilatora.
Blrfl
1
Nie zapomnij:const CHAR_PTR mutable_pointer_to_const_char; const char_ptr const_pointer_to_mutable_char;
Jon Purdy,
3
Definicja powoduje również, że komunikaty o błędach stają się niezrozumiałe
Thomas Bonini
Przykład bólu, który może powodować niewłaściwe użycie makr (choć nie dotyczy bezpośrednio makr vs. typedefs
Keith Thompson
15

Zdecydowanie największym problemem z makrami jest to, że nie mają one zasięgu. Już samo to uzasadnia użycie typedef. Ponadto jest to bardziej wyraźne semantycznie. Kiedy ktoś, kto czyta twój kod, zobaczy definicję, nie będzie wiedział, o co chodzi, dopóki go nie przeczyta i nie zrozumie całego makra. Typedef informuje czytelnika, że ​​nazwa typu zostanie zdefiniowana. (Powinienem wspomnieć, że mówię o C ++. Nie jestem pewien co do zakresu typedef dla C, ale myślę, że jest podobnie).

Tamás Szelei
źródło
Ale jak pokazałem w przykładzie podanym w pytaniu, typedef i #define używają tej samej liczby słów! Te 3 słowa makra nie powodują żadnych nieporozumień, ponieważ słowa są takie same, ale po prostu uporządkowane. :)
c0da
2
Tak, ale nie chodzi o krótkość. Makro może robić wiele innych rzeczy oprócz pisania na klawiaturze. Ale osobiście uważam, że najważniejsze jest określenie zakresu.
Tamás Szelei
2
@ c0da - nie powinieneś używać makr dla typedefs, powinieneś używać typedefs dla typedefs. Rzeczywisty efekt makra może być bardzo różny, co ilustrują różne odpowiedzi.
Joris Timmermans
15

Kod źródłowy jest napisany głównie dla innych programistów. Komputery używają skompilowanej wersji.

W tej perspektywie typedefma znaczenie, które #definenie.

mouviciel
źródło
6

Odpowiedź Keitha Thompsona jest bardzo dobra i wraz z dodatkowymi uwagami Tamása Szelei na temat ustalania zakresu powinny zapewnić wszystkie potrzebne tła.

Zawsze powinieneś uważać makra za ostateczność zdesperowanych. Są pewne rzeczy, które możesz zrobić tylko z makrami. Nawet wtedy powinieneś myśleć długo i ciężko, jeśli naprawdę chcesz to zrobić. Ilość bólu związanego z debugowaniem, który może być spowodowany przez subtelnie uszkodzone makra, jest znaczna i będziesz musiał przeglądać wstępnie przetworzone pliki. Warto to zrobić tylko raz, aby poznać zakres problemu w C ++ - tylko rozmiar wstępnie przetworzonego pliku może przyciągnąć wzrok.

Joris Timmermans
źródło
5

Oprócz wszystkich podanych już wcześniej uwag na temat zamiany zakresu i tekstu, #define również nie jest w pełni kompatybilny z typedef! Mianowicie, jeśli chodzi o wskaźniki funkcji.

typedef void(*fptr_t)(void);

To deklaruje typ, fptr_tktóry jest wskaźnikiem do funkcji typu void func(void).

Nie można zadeklarować tego typu za pomocą makra. #define fptr_t void(*)(void)oczywiście nie zadziała. Będziesz musiał napisać coś niejasnego #define fptr_t(name) void(*name)(void), co tak naprawdę nie ma sensu w języku C, w którym nie mamy konstruktorów.

Wskaźników tablicy nie można zadeklarować za pomocą #define: typedef int(*arr_ptr)[10];


I chociaż C nie ma obsługi języka dla bezpieczeństwa typów, o których warto wspomnieć, innym przypadkiem, w którym typedef i #define nie są kompatybilne, jest przypadek podejrzanej konwersji typu. Kompilator i / lub narzędzie analizatora astatycznego może wyświetlać ostrzeżenia o takich konwersjach, jeśli użyjesz polecenia typedef.


źródło
1
Jeszcze lepsze są kombinacje wskaźników funkcji i tablicy, takie jak char *(*(*foo())[])();( foofunkcja zwraca wskaźnik do tablicy wskaźników do funkcji zwracających wskaźnik char).
John Bode
4

Używaj narzędzia o najmniejszej mocy, która wystarcza do wykonania zadania, i narzędzia o największej liczbie ostrzeżeń. #define jest oceniane w preprocesorze, jesteś tam w dużej mierze sam. Typedef jest oceniany przez kompilator. Podawane są kontrole i typedef może definiować tylko typy, jak sama nazwa wskazuje. Więc w twoim przykładzie zdecydowanie wybierz typedef.

Jaskółka oknówka
źródło
2

Oprócz zwykłych argumentów przeciw definicji, jak napisałbyś tę funkcję za pomocą makr?

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

Jest to oczywiście trywialny przykład, ale ta funkcja może sumować się z dowolną iteracyjną sekwencją do przodu typu, który implementuje dodawanie *. Typedefy używane w ten sposób są ważnym elementem programowania ogólnego .

* Właśnie to napisałem tutaj, pozostawiam kompilację jako ćwiczenie dla czytelnika :-)

EDYTOWAĆ:

Ta odpowiedź wydaje się być źródłem zamieszania, więc pozwólcie, że wyciągnę ją jeszcze bardziej. Jeśli zajrzysz do definicji wektora STL, zobaczysz coś podobnego do następującego:

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

Zastosowanie typedefs w standardowych kontenerach pozwala na ogólną funkcję (taką jak ta, którą utworzyłem powyżej) na odniesienie do tych typów. Funkcja „Suma” jest wzorowana na typie kontenera ( std::vector<int>), a nie na typie przechowywanym wewnątrz kontenera ( int). Bez typedef odniesienie do tego typu wewnętrznego nie byłoby możliwe.

Dlatego typedefs są kluczowe dla Modern C ++ i nie jest to możliwe w przypadku makr.

Chris Pitman
źródło
Pytanie o makra vs typedef, a nie makra vs funkcje wbudowane.
Ben Voigt,
Jasne, i jest to możliwe tylko w przypadku typedefs ... to właśnie jest typ_wartości.
Chris Pitman
To nie jest typedef, to programowanie szablonów. Funkcja lub funktor nie jest typem, bez względu na to, jak jest ogólny.
@Lundin Dodałem dalsze szczegóły: Funkcja Suma jest możliwa tylko dlatego, że standardowe kontenery używają typedefs do ujawnienia typów, na których są wzorowane. Ja nie mówiąc, że suma jest typedef. Mówię, że std :: vector <T> :: value_type jest typedef. Zapoznaj się ze źródłem dowolnej standardowej implementacji biblioteki do weryfikacji.
Chris Pitman
Słusznie. Być może lepiej byłoby opublikować tylko drugi fragment kodu.
1

typedefpasuje do filozofii C ++: wszystkie możliwe kontrole / zapewnienia w czasie kompilacji. #defineto tylko sztuczka preprocesora, która ukrywa wiele kompilacji semantycznej. Nie powinieneś martwić się o wydajność kompilacji bardziej niż o poprawność kodu.

Tworząc nowy typ, definiujesz nową „rzecz”, którą manipuluje domena twojego programu. Możesz więc użyć tej „rzeczy” do komponowania funkcji i klas, a kompilatora możesz użyć do wykonywania kontroli statycznych. W każdym razie, ponieważ C ++ jest kompatybilny z C, istnieje wiele niejawnych konwersji między inttypami opartymi na int, które nie generują ostrzeżeń. W tym przypadku nie masz pełnej mocy kontroli statycznych. Możesz jednak zastąpić swój typedef, enumaby znaleźć niejawne konwersje. Np .: Jeśli tak typedef int Age;, możesz zamienić na enum Age { };i będziesz otrzymywać wszelkiego rodzaju błędy przez niejawne konwersje między Agei int.

Kolejna rzecz: typedefmoże być w środku namespace.

dacap
źródło
1

Używanie definicji zamiast typów jest kłopotliwe w innym ważnym aspekcie, a mianowicie w koncepcji cech typu. Rozważ różne klasy (pomyśl o standardowych kontenerach), z których wszystkie definiują określone typy czcionek. Możesz pisać kod ogólny, odwołując się do typedefs. Przykłady obejmują ogólne wymagania dotyczące kontenerów (standard c ++ 23.2.1 [container.requirements.general]), takie jak

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

Wszystko to nie da się wyrazić w sposób ogólny za pomocą makra, ponieważ nie ma zasięgu.

Francesco
źródło
1

Nie zapomnij o konsekwencjach tego dla twojego debuggera. Niektóre debuggery nie radzą sobie zbyt dobrze z #define. Korzystaj z obu w debugerze, którego używasz. Pamiętaj, że poświęcisz więcej czasu na czytanie niż na pisanie.

zaraz
źródło
-2

„#define” zastąpi to, co napisałeś później, typedef utworzy typ. Więc jeśli chcesz mieć typ klienta - użyj typedef. Jeśli potrzebujesz makr - użyj definicji.

Dainius
źródło
Uwielbiam ludzi, którzy rezygnują z głosowania, a nawet nie zawracają sobie głowy pisaniem komentarza.
Dainius