ketikkan dan # definisi

32

Kita semua pasti telah menggunakan typedefdan #defines satu waktu atau yang lain. Hari ini ketika bekerja dengan mereka, saya mulai merenungkan sesuatu.

Pertimbangkan 2 situasi di bawah ini untuk menggunakan inttipe data dengan nama lain:

typedef int MYINTEGER

dan

#define MYINTEGER int

Seperti situasi di atas, kita dapat, dalam banyak situasi, menyelesaikan dengan sangat baik menggunakan #define, dan juga melakukan hal yang sama dengan menggunakan typedef, walaupun cara kita melakukan hal yang sama mungkin sangat berbeda. #define juga dapat melakukan tindakan MACRO yang tidak bisa dilakukan typedef.

Meskipun alasan dasar untuk menggunakannya adalah berbeda, seberapa berbeda kerjanya? Kapan sebaiknya satu lebih disukai dari yang lain ketika keduanya dapat digunakan? Juga, apakah satu dijamin lebih cepat daripada yang lain dalam situasi apa? (misalnya, #define adalah arahan preprosesor, jadi semuanya dilakukan jauh lebih awal daripada saat kompilasi atau runtime).

c0da
sumber
8
Gunakan #define untuk tujuan apa mereka dirancang. Kompilasi bersyarat berdasarkan properti yang Anda tidak bisa tahu ketika menulis kode (seperti OS / Compiler). Gunakan konstruksi bahasa sebaliknya.
Martin York

Jawaban:

67

A typedefumumnya lebih disukai kecuali ada beberapa alasan aneh bahwa Anda secara khusus membutuhkan makro.

macro melakukan substitusi teks, yang dapat melakukan kekerasan yang cukup besar pada semantik kode. Misalnya, diberikan:

#define MYINTEGER int

Anda dapat secara legal menulis:

short MYINTEGER x = 42;

karena short MYINTEGERmengembang ke short int.

Di sisi lain, dengan typedef:

typedef int MYINTEGER:

nama MYINTEGERadalah nama lain untuk jenis int , bukan pengganti teks untuk kata kunci "int".

Hal-hal menjadi lebih buruk dengan tipe yang lebih rumit. Misalnya, diberikan ini:

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

a,, bdan csemuanya adalah pointer, tetapi da char, karena baris terakhir diperluas ke:

char* c, d;

yang setara dengan

char *c;
char d;

(Typedef untuk tipe pointer biasanya bukan ide yang baik, tetapi ini menggambarkan intinya.)

Kasus aneh lain:

#define DWORD long
DWORD double x;     /* Huh? */
Keith Thompson
sumber
1
Pointer untuk disalahkan lagi! :) Ada contoh lain selain pointer di mana tidak bijaksana untuk menggunakan macro seperti itu?
c0da
2
Bukan berarti aku pernah menggunakan makro di tempat dari typedef, tapi cara yang tepat untuk membangun sebuah makro yang melakukan sesuatu dengan apa pun berikut adalah dengan menggunakan argumen: #define CHAR_PTR(x) char *x. Ini setidaknya akan menyebabkan kompiler untuk mengambil jika digunakan secara tidak benar.
Blrfl
1
Jangan lupa:const CHAR_PTR mutable_pointer_to_const_char; const char_ptr const_pointer_to_mutable_char;
Jon Purdy
3
Tentukan juga membuat pesan kesalahan tidak dapat dipahami
Thomas Bonini
Contoh rasa sakit yang dapat disebabkan oleh penyalahgunaan makro (meskipun tidak secara langsung relevan dengan makro vs typedef): github.com/Keith-S-Thompson/42
Keith Thompson
15

Sejauh ini, masalah terbesar dengan makro adalah bahwa mereka tidak dicakup. Itu saja menjamin penggunaan typedef. Juga, ini lebih jelas secara semantik. Ketika seseorang yang membaca kode Anda melihat sebuah definisi, ia tidak akan tahu tentang apa itu sampai ia membacanya dan memahami keseluruhan makro. Typedef memberi tahu pembaca bahwa nama tipe akan ditentukan. (Saya harus menyebutkan bahwa saya berbicara tentang C ++. Saya tidak yakin tentang scoping typedef untuk C, tapi saya kira itu mirip).

Tamás Szelei
sumber
Tetapi seperti yang saya tunjukkan dalam contoh yang disediakan dalam pertanyaan, typedef dan #define menggunakan jumlah kata yang sama! Juga, 3 kata makro ini tidak akan menimbulkan kebingungan, karena kata-katanya sama, tetapi hanya disusun ulang. :)
c0da
2
Ya, tapi ini bukan tentang kekurangan. Makro dapat melakukan banyak hal lain selain mengetik. Tetapi secara pribadi saya pikir pelingkupan adalah yang paling penting.
Tamás Szelei
2
@ c0da - Anda tidak boleh menggunakan makro untuk typedefs, Anda harus menggunakan typedefs untuk typedefs. Efek sebenarnya dari makro bisa sangat berbeda, seperti yang diilustrasikan oleh berbagai respons.
Joris Timmermans
15

Kode sumber terutama ditulis untuk sesama pengembang. Komputer menggunakan versi kompilasi itu.

Dalam perspektif ini typedefmembawa makna yang #definetidak.

mouviciel
sumber
6

Jawaban Keith Thompson sangat baik dan bersama dengan poin tambahan Tamás Szelei tentang pelingkupan harus memberikan semua latar belakang yang Anda butuhkan.

Anda harus selalu menganggap makro sebagai pilihan terakhir bagi mereka yang putus asa. Ada beberapa hal tertentu yang hanya dapat Anda lakukan dengan makro. Meski begitu, Anda harus berpikir panjang dan keras jika Anda benar-benar ingin melakukannya. Jumlah rasa sakit dalam debugging yang dapat disebabkan oleh makro yang rusak cukup besar, dan Anda harus mencari melalui file yang telah diproses. Sebaiknya lakukan itu sekali saja, untuk mengetahui lingkup masalah dalam C ++ - hanya ukuran file yang sudah diproses mungkin menjadi pembuka mata.

Joris Timmermans
sumber
5

Selain semua komentar yang valid tentang ruang lingkup dan penggantian teks yang sudah diberikan, #define juga tidak sepenuhnya kompatibel dengan typedef! Yaitu ketika datang ke kasus pointer fungsi.

typedef void(*fptr_t)(void);

Ini menyatakan tipe fptr_tyang merupakan penunjuk ke fungsi tipe void func(void).

Anda tidak dapat mendeklarasikan tipe ini dengan makro. #define fptr_t void(*)(void)jelas tidak akan bekerja. Anda harus menulis sesuatu yang tidak jelas #define fptr_t(name) void(*name)(void), yang benar-benar tidak masuk akal dalam bahasa C, di mana kami tidak memiliki konstruktor.

Array pointer tidak dapat dideklarasikan dengan #define: typedef int(*arr_ptr)[10];


Dan sementara C tidak memiliki dukungan bahasa untuk keamanan jenis yang layak disebutkan, kasus lain di mana typedef dan #define tidak kompatibel adalah ketika Anda melakukan konversi jenis yang mencurigakan. Kompilator dan / atau alat analisis astatic mungkin dapat memberikan peringatan untuk konversi tersebut jika Anda menggunakan typedef.


sumber
1
Yang lebih baik lagi adalah kombinasi fungsi dan pointer array, seperti char *(*(*foo())[])();( fooadalah fungsi yang mengembalikan pointer ke array pointer ke fungsi yang mengembalikan pointer ke char).
John Bode
4

Gunakan alat dengan daya paling sedikit yang menyelesaikan pekerjaan, dan yang paling banyak peringatan. #define dievaluasi di preprocessor, Anda sebagian besar sendirian di sana. typedef dievaluasi oleh kompiler. Cek diberikan dan typedef hanya bisa mendefinisikan tipe, seperti namanya. Jadi, dalam contoh Anda pasti pergi untuk typedef.

Martin
sumber
2

Di luar argumen normal terhadap define, bagaimana Anda menulis fungsi ini menggunakan 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);

Ini jelas merupakan contoh sepele, tetapi fungsi itu dapat menjumlahkan setiap urutan maju maju dari jenis yang mengimplementasikan penambahan *. Typedef yang digunakan seperti ini adalah elemen penting dari Generic Programming .

* Saya baru saja menulis ini di sini, saya meninggalkan kompilasi sebagai latihan untuk pembaca :-)

EDIT:

Jawaban ini tampaknya menyebabkan banyak kebingungan, jadi izinkan saya menjelaskannya lebih lanjut. Jika Anda melihat ke dalam definisi vektor STL, Anda akan melihat sesuatu yang mirip dengan yang berikut:

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

Penggunaan typedef dalam wadah standar memungkinkan fungsi generik (seperti yang saya buat di atas) untuk referensi jenis ini. Fungsi "Jumlah" templated pada jenis wadah ( std::vector<int>), bukan pada jenis yang disimpan di dalam wadah ( int). Tanpa typedef, tidak mungkin untuk merujuk pada tipe internal itu.

Oleh karena itu, typedef sangat penting bagi Modern C ++ dan ini tidak dimungkinkan dengan makro.

Chris Pitman
sumber
Pertanyaan yang diajukan tentang makro vs typedef, bukan makro vs fungsi sebaris.
Ben Voigt
Tentu, dan ini hanya mungkin dengan typedefs ... itulah value_type itu.
Chris Pitman
Ini bukan typedef, ini adalah pemrograman templat. Fungsi atau functor bukan tipe, tidak peduli seberapa generiknya.
@Lundin Saya telah menambahkan rincian lebih lanjut: Fungsi Sum hanya mungkin karena kontainer standar menggunakan typedef untuk mengekspos jenis tempat templated mereka. Saya tidak mengatakan bahwa Sum adalah typedef. Saya mengatakan std :: vector <T> :: value_type adalah typedef. Jangan ragu untuk melihat sumber dari implementasi perpustakaan standar untuk memverifikasi.
Chris Pitman
Cukup adil. Mungkin akan lebih baik untuk hanya mengirim cuplikan kedua.
1

typedefcocok dengan filosofi C ++: semua kemungkinan pemeriksaan / konfirmasi dalam waktu kompilasi. #definehanyalah trik preprosesor yang menyembunyikan banyak semantik ke kompiler. Anda tidak perlu khawatir tentang kinerja kompilasi lebih dari kebenaran kode.

Saat Anda membuat tipe baru, Anda mendefinisikan "hal" baru yang dimanipulasi domain program Anda. Jadi Anda dapat menggunakan "hal" ini untuk membuat fungsi dan kelas, dan Anda dapat menggunakan kompiler untuk keuntungan Anda untuk melakukan pemeriksaan statis. Bagaimanapun, karena C ++ kompatibel dengan C, ada banyak konversi implisit antara intdan tipe berbasis int yang tidak menghasilkan peringatan. Jadi Anda tidak mendapatkan kekuatan penuh dari pemeriksaan statis dalam kasus ini. Namun, Anda dapat mengganti typedefdengan dengan enumuntuk menemukan konversi tersirat. Misalnya: Jika sudah typedef int Age;, maka Anda dapat mengganti dengan enum Age { };dan Anda akan mendapatkan semua jenis kesalahan dengan konversi tersirat antara Agedan int.

Hal lain: typedefbisa di dalam a namespace.

dasbor
sumber
1

Menggunakan mendefinisikan bukan typedefs bermasalah di bawah aspek penting lainnya, dan yaitu konsep ciri-ciri tipe. Pertimbangkan kelas yang berbeda (pikirkan wadah standar) yang semuanya menentukan jenis spesifik mereka. Anda dapat menulis kode generik dengan merujuk pada typedefs. Contohnya termasuk persyaratan wadah umum (c ++ standar 23.2.1 [container.requirements.general]) seperti

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

Semua ini tidak dapat diekspresikan secara umum dengan makro karena tidak dicakup.

Francesco
sumber
1

Jangan lupa implikasi ini pada debugger Anda. Beberapa debugger tidak menangani #define dengan baik. Bermain-main dengan keduanya di debugger yang Anda gunakan. Ingat, Anda akan menghabiskan lebih banyak waktu untuk membacanya daripada menulisnya.

segera
sumber
-2

"#define" akan menggantikan apa yang Anda tulis setelah itu, typedef akan membuat jenis. Jadi jika Anda ingin memiliki tipe custome - gunakan typedef. Jika Anda membutuhkan makro - gunakan define.

Dainius
sumber
Saya suka orang yang tidak memilih dan bahkan tidak mau menulis komentar.
Dainius