Terlepas dari kenyataan makro Anda adalah intdan Anda constexpr unsignedadalah unsigned, ada perbedaan penting dan macro hanya memiliki satu keuntungan.
Cakupan
Makro didefinisikan oleh preprocessor dan hanya diganti ke dalam kode setiap kali itu terjadi. Preprocessor bodoh dan tidak memahami sintaks atau semantik C ++. Makro mengabaikan cakupan seperti ruang nama, kelas, atau blok fungsi, sehingga Anda tidak dapat menggunakan nama untuk hal lain dalam file sumber. Itu tidak benar untuk konstanta yang didefinisikan sebagai variabel C ++ yang tepat:
Tidak masalah untuk memanggil variabel anggota max_heightkarena ini adalah anggota kelas dan memiliki cakupan yang berbeda, dan berbeda dari yang ada di ruang lingkup namespace. Jika Anda mencoba menggunakan kembali nama MAX_HEIGHTanggota tersebut, maka preprocessor akan mengubahnya menjadi omong kosong yang tidak dapat dikompilasi:
classWindow {// ...int720;
};
Inilah sebabnya mengapa Anda harus memberikan makro UGLY_SHOUTY_NAMESuntuk memastikannya menonjol dan Anda dapat berhati-hati dalam menamainya untuk menghindari bentrokan. Jika Anda tidak menggunakan makro secara tidak perlu, Anda tidak perlu khawatir tentang itu (dan tidak perlu membaca SHOUTY_NAMES).
Jika Anda hanya menginginkan konstanta di dalam fungsi, Anda tidak dapat melakukannya dengan makro, karena preprocessor tidak tahu apa itu fungsi atau apa artinya berada di dalamnya. Untuk membatasi makro hanya ke bagian tertentu dari file, Anda memerlukannya #undeflagi:
Variabel constexpr adalah variabel sehingga sebenarnya ada dalam program dan Anda dapat melakukan hal-hal C ++ normal seperti mengambil alamatnya dan mengikat referensi ke sana.
Masalahnya adalah itu MAX_HEIGHTbukan variabel, jadi untuk panggilan std::maxsementara intharus dibuat oleh kompilator. Referensi yang dikembalikan oleh std::maxmungkin kemudian merujuk ke sementara itu, yang tidak ada setelah akhir pernyataan itu, jadi return hmengakses memori yang tidak valid.
Masalah itu tidak ada dengan variabel yang tepat, karena memiliki lokasi tetap dalam memori yang tidak hilang:
Anda tidak dapat menggunakan variabel di sini, karena preprocessor tidak memahami cara merujuk ke variabel dengan nama. Ini hanya memahami hal-hal dasar yang sangat mendasar seperti ekspansi makro dan arahan yang dimulai dengan #(suka #includedan #definedan #if).
Jika anda menginginkan sebuah konstanta yang dapat dipahami oleh preprocessor maka anda harus menggunakan preprocessor untuk mendefinisikannya. Jika Anda menginginkan konstanta untuk kode C ++ normal, gunakan kode C ++ normal.
Contoh di atas hanya untuk mendemonstrasikan kondisi preprocessor, tetapi bahkan kode tersebut dapat menghindari penggunaan preprocessor:
using height_type = std::conditional_t<max_height < 256, unsignedchar, unsignedint>;
Sebuah constexprvariabel tidak perlu menempati memori sampai alamatnya (pointer / referensi) diambil; jika tidak, itu dapat dioptimalkan sepenuhnya (dan saya pikir mungkin ada Standardese yang menjamin itu). Saya ingin menekankan hal ini agar orang tidak terus menggunakan ' enumretasan' lama yang lebih rendah dari gagasan yang salah arah bahwa hal sepele constexpryang tidak memerlukan penyimpanan akan tetap menempati sebagian.
underscore_d
3
Bagian "A real memory location" Anda salah: 1. Anda mengembalikan dengan value (int), jadi salinan dibuat, sementara tidak menjadi masalah. 2. Seandainya Anda mengembalikan dengan referensi (int &), maka masalah Anda int heightakan sama seperti makro, karena cakupannya terkait dengan fungsi, pada dasarnya juga sementara. 3. Komentar di atas, "const int & h akan memperpanjang masa hidup sementara" sudah benar.
PoweredByRice
4
@underscore_d true, tapi itu tidak mengubah argumen. Variabel tidak memerlukan penyimpanan kecuali ada penggunaan odr-nya. Intinya adalah ketika variabel nyata dengan penyimpanan diperlukan, variabel constexpr melakukan hal yang benar.
Jonathan Wakely
1
@PoweredByRice 1. masalah tidak ada hubungannya dengan nilai kembali limit, masalahnya adalah nilai kembali std::max. 2. ya, itu sebabnya tidak mengembalikan referensi. 3. salah lihat link coliru diatas.
Jonathan Wakely
3
@PoweredByRice menghela napas, Anda benar-benar tidak perlu menjelaskan cara kerja C ++ kepada saya. Jika Anda memiliki const int& h = max(x, y);dan maxmengembalikan dengan nilai seumur hidup nilai pengembaliannya diperpanjang. Bukan oleh tipe kembalian, tapi oleh tipe const int&itu terikat. Apa yang saya tulis benar.
Jonathan Wakely
11
Secara umum, Anda harus menggunakan constexprkapan pun Anda bisa, dan makro hanya jika tidak ada solusi lain yang memungkinkan.
Alasan:
Makro adalah pengganti sederhana dalam kode, dan karena alasan ini, maxmakro sering kali menimbulkan konflik (misalnya makro windows.h vs std::max). Selain itu, makro yang berfungsi dapat dengan mudah digunakan dengan cara berbeda yang kemudian dapat memicu kesalahan kompilasi yang aneh. (misalnya Q_PROPERTYdigunakan pada anggota struktur)
Karena semua ketidakpastian tersebut, merupakan gaya kode yang baik untuk menghindari makro, persis seperti biasanya Anda menghindari gotos.
constexpr didefinisikan secara semantik, dan dengan demikian biasanya menghasilkan masalah yang jauh lebih sedikit.
Dalam kasus apa menggunakan makro tidak dapat dihindari?
Tom Dorone
3
Kompilasi bersyarat menggunakan #ifhal-hal yang sebenarnya berguna bagi preprocessor. Mendefinisikan sebuah konstanta bukanlah salah satu hal yang berguna untuk preprocessor, kecuali konstanta itu harus berupa makro karena digunakan dalam kondisi preprocessor menggunakan #if. Jika konstanta digunakan dalam kode C ++ normal (bukan arahan preprocessor), maka gunakan variabel C ++ normal, bukan makro preprocessor.
Jonathan Wakely
Kecuali menggunakan makro variadic, sebagian besar penggunaan makro untuk sakelar kompilator, tetapi mencoba mengganti pernyataan makro saat ini (seperti sakelar bersyarat, string literal) berurusan dengan pernyataan kode nyata dengan constexpr adalah ide yang bagus?
Saya akan mengatakan switch kompiler bukanlah ide yang baik juga. Namun, saya memahami sepenuhnya bahwa ini diperlukan beberapa kali (juga makro), terutama yang berhubungan dengan kode lintas platform atau yang disematkan. Untuk menjawab pertanyaan Anda: Jika Anda sudah berurusan dengan preprocessor, saya akan menggunakan makro agar tetap jelas dan intuitif apa itu preprocessor dan apa itu waktu kompilasi. Saya juga menyarankan untuk memberi komentar yang berat dan membuatnya digunakan sesingkat dan sesingkat mungkin (hindari makro menyebar atau 100 baris #if). Mungkin pengecualiannya adalah penjaga #ifndef yang khas (standar untuk #pragma sekali) yang dipahami dengan baik.
Adrian Maire
3
Jawaban bagus dari Jonathon Wakely . Saya juga menyarankan Anda untuk melihat jawaban jogojapan tentang apa perbedaan antara constdan constexprbahkan sebelum Anda mempertimbangkan penggunaan makro.
Makro itu bodoh, tetapi dalam arti yang baik . Saat ini seolah-olah mereka adalah bantuan pembangunan ketika Anda ingin bagian yang sangat spesifik dari kode Anda hanya dikompilasi dengan adanya parameter build tertentu yang "ditentukan". Biasanya, semua yang berarti mengambil nama makro Anda, atau lebih baik lagi, mari kita sebut saja Trigger, dan menambahkan hal-hal seperti, /D:Trigger, -DTrigger, dll untuk alat membangun yang digunakan.
Meskipun ada banyak kegunaan yang berbeda untuk makro, ini adalah dua yang paling sering saya lihat yang bukan praktik yang buruk / ketinggalan zaman:
Bagian kode khusus Perangkat Keras dan Platform
Peningkatan verbositas membangun
Jadi sementara Anda dapat dalam kasus OP mencapai tujuan yang sama untuk mendefinisikan int dengan constexpratau a MACRO, tidak mungkin keduanya akan tumpang tindih saat menggunakan konvensi modern. Berikut beberapa penggunaan makro umum yang belum dihentikan secara bertahap.
#if defined VERBOSE || defined DEBUG || defined MSG_ALL// Verbose message-handling code here#endif
Sebagai contoh lain untuk penggunaan makro, katakanlah Anda memiliki beberapa perangkat keras yang akan datang untuk dirilis, atau mungkin generasi tertentu darinya yang memiliki beberapa solusi rumit yang tidak diperlukan yang lain. Kami akan mendefinisikan makro ini sebagai GEN_3_HW.
#if defined GEN_3_HW && defined _WIN64// Windows-only special handling for 64-bit upcoming hardware#elif defined GEN_3_HW && defined __APPLE__// Special handling for macs on the new hardware#elif !defined _WIN32 && !defined __linux__ && !defined __APPLE__ && !defined __ANDROID__ && !defined __unix__ && !defined __arm__// Greetings, Outlander! ;)#else// Generic handling#endif
Jawaban:
Tidak. Sama sekali tidak. Bahkan tidak dekat.
Terlepas dari kenyataan makro Anda adalah
int
dan Andaconstexpr unsigned
adalahunsigned
, ada perbedaan penting dan macro hanya memiliki satu keuntungan.Cakupan
Makro didefinisikan oleh preprocessor dan hanya diganti ke dalam kode setiap kali itu terjadi. Preprocessor bodoh dan tidak memahami sintaks atau semantik C ++. Makro mengabaikan cakupan seperti ruang nama, kelas, atau blok fungsi, sehingga Anda tidak dapat menggunakan nama untuk hal lain dalam file sumber. Itu tidak benar untuk konstanta yang didefinisikan sebagai variabel C ++ yang tepat:
#define MAX_HEIGHT 720 constexpr int max_height = 720; class Window { // ... int max_height; };
Tidak masalah untuk memanggil variabel anggota
max_height
karena ini adalah anggota kelas dan memiliki cakupan yang berbeda, dan berbeda dari yang ada di ruang lingkup namespace. Jika Anda mencoba menggunakan kembali namaMAX_HEIGHT
anggota tersebut, maka preprocessor akan mengubahnya menjadi omong kosong yang tidak dapat dikompilasi:class Window { // ... int 720; };
Inilah sebabnya mengapa Anda harus memberikan makro
UGLY_SHOUTY_NAMES
untuk memastikannya menonjol dan Anda dapat berhati-hati dalam menamainya untuk menghindari bentrokan. Jika Anda tidak menggunakan makro secara tidak perlu, Anda tidak perlu khawatir tentang itu (dan tidak perlu membacaSHOUTY_NAMES
).Jika Anda hanya menginginkan konstanta di dalam fungsi, Anda tidak dapat melakukannya dengan makro, karena preprocessor tidak tahu apa itu fungsi atau apa artinya berada di dalamnya. Untuk membatasi makro hanya ke bagian tertentu dari file, Anda memerlukannya
#undef
lagi:int limit(int height) { #define MAX_HEIGHT 720 return std::max(height, MAX_HEIGHT); #undef MAX_HEIGHT }
Bandingkan dengan yang jauh lebih masuk akal:
int limit(int height) { constexpr int max_height = 720; return std::max(height, max_height); }
Mengapa Anda lebih memilih yang makro?
Lokasi memori yang nyata
Variabel constexpr adalah variabel sehingga sebenarnya ada dalam program dan Anda dapat melakukan hal-hal C ++ normal seperti mengambil alamatnya dan mengikat referensi ke sana.
Kode ini memiliki perilaku yang tidak ditentukan:
#define MAX_HEIGHT 720 int limit(int height) { const int& h = std::max(height, MAX_HEIGHT); // ... return h; }
Masalahnya adalah itu
MAX_HEIGHT
bukan variabel, jadi untuk panggilanstd::max
sementaraint
harus dibuat oleh kompilator. Referensi yang dikembalikan olehstd::max
mungkin kemudian merujuk ke sementara itu, yang tidak ada setelah akhir pernyataan itu, jadireturn h
mengakses memori yang tidak valid.Masalah itu tidak ada dengan variabel yang tepat, karena memiliki lokasi tetap dalam memori yang tidak hilang:
int limit(int height) { constexpr int max_height = 720; const int& h = std::max(height, max_height); // ... return h; }
(Dalam praktiknya Anda mungkin akan menyatakan
int h
tidak,const int& h
tetapi masalahnya dapat muncul dalam konteks yang lebih halus.)Kondisi praprosesor
Satu-satunya waktu untuk memilih makro adalah saat Anda ingin nilainya dipahami oleh preprocessor, untuk digunakan dalam
#if
kondisi, mis.#define MAX_HEIGHT 720 #if MAX_HEIGHT < 256 using height_type = unsigned char; #else using height_type = unsigned int; #endif
Anda tidak dapat menggunakan variabel di sini, karena preprocessor tidak memahami cara merujuk ke variabel dengan nama. Ini hanya memahami hal-hal dasar yang sangat mendasar seperti ekspansi makro dan arahan yang dimulai dengan
#
(suka#include
dan#define
dan#if
).Jika anda menginginkan sebuah konstanta yang dapat dipahami oleh preprocessor maka anda harus menggunakan preprocessor untuk mendefinisikannya. Jika Anda menginginkan konstanta untuk kode C ++ normal, gunakan kode C ++ normal.
Contoh di atas hanya untuk mendemonstrasikan kondisi preprocessor, tetapi bahkan kode tersebut dapat menghindari penggunaan preprocessor:
using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
sumber
constexpr
variabel tidak perlu menempati memori sampai alamatnya (pointer / referensi) diambil; jika tidak, itu dapat dioptimalkan sepenuhnya (dan saya pikir mungkin ada Standardese yang menjamin itu). Saya ingin menekankan hal ini agar orang tidak terus menggunakan 'enum
retasan' lama yang lebih rendah dari gagasan yang salah arah bahwa hal sepeleconstexpr
yang tidak memerlukan penyimpanan akan tetap menempati sebagian.int height
akan sama seperti makro, karena cakupannya terkait dengan fungsi, pada dasarnya juga sementara. 3. Komentar di atas, "const int & h akan memperpanjang masa hidup sementara" sudah benar.limit
, masalahnya adalah nilai kembalistd::max
. 2. ya, itu sebabnya tidak mengembalikan referensi. 3. salah lihat link coliru diatas.const int& h = max(x, y);
danmax
mengembalikan dengan nilai seumur hidup nilai pengembaliannya diperpanjang. Bukan oleh tipe kembalian, tapi oleh tipeconst int&
itu terikat. Apa yang saya tulis benar.Secara umum, Anda harus menggunakan
constexpr
kapan pun Anda bisa, dan makro hanya jika tidak ada solusi lain yang memungkinkan.Alasan:
Makro adalah pengganti sederhana dalam kode, dan karena alasan ini,
max
makro sering kali menimbulkan konflik (misalnya makro windows.h vsstd::max
). Selain itu, makro yang berfungsi dapat dengan mudah digunakan dengan cara berbeda yang kemudian dapat memicu kesalahan kompilasi yang aneh. (misalnyaQ_PROPERTY
digunakan pada anggota struktur)Karena semua ketidakpastian tersebut, merupakan gaya kode yang baik untuk menghindari makro, persis seperti biasanya Anda menghindari gotos.
constexpr
didefinisikan secara semantik, dan dengan demikian biasanya menghasilkan masalah yang jauh lebih sedikit.sumber
#if
hal-hal yang sebenarnya berguna bagi preprocessor. Mendefinisikan sebuah konstanta bukanlah salah satu hal yang berguna untuk preprocessor, kecuali konstanta itu harus berupa makro karena digunakan dalam kondisi preprocessor menggunakan#if
. Jika konstanta digunakan dalam kode C ++ normal (bukan arahan preprocessor), maka gunakan variabel C ++ normal, bukan makro preprocessor.Jawaban bagus dari Jonathon Wakely . Saya juga menyarankan Anda untuk melihat jawaban jogojapan tentang apa perbedaan antara
const
danconstexpr
bahkan sebelum Anda mempertimbangkan penggunaan makro.Makro itu bodoh, tetapi dalam arti yang baik . Saat ini seolah-olah mereka adalah bantuan pembangunan ketika Anda ingin bagian yang sangat spesifik dari kode Anda hanya dikompilasi dengan adanya parameter build tertentu yang "ditentukan". Biasanya, semua yang berarti mengambil nama makro Anda, atau lebih baik lagi, mari kita sebut saja
Trigger
, dan menambahkan hal-hal seperti,/D:Trigger
,-DTrigger
, dll untuk alat membangun yang digunakan.Meskipun ada banyak kegunaan yang berbeda untuk makro, ini adalah dua yang paling sering saya lihat yang bukan praktik yang buruk / ketinggalan zaman:
Jadi sementara Anda dapat dalam kasus OP mencapai tujuan yang sama untuk mendefinisikan int dengan
constexpr
atau aMACRO
, tidak mungkin keduanya akan tumpang tindih saat menggunakan konvensi modern. Berikut beberapa penggunaan makro umum yang belum dihentikan secara bertahap.#if defined VERBOSE || defined DEBUG || defined MSG_ALL // Verbose message-handling code here #endif
Sebagai contoh lain untuk penggunaan makro, katakanlah Anda memiliki beberapa perangkat keras yang akan datang untuk dirilis, atau mungkin generasi tertentu darinya yang memiliki beberapa solusi rumit yang tidak diperlukan yang lain. Kami akan mendefinisikan makro ini sebagai
GEN_3_HW
.#if defined GEN_3_HW && defined _WIN64 // Windows-only special handling for 64-bit upcoming hardware #elif defined GEN_3_HW && defined __APPLE__ // Special handling for macs on the new hardware #elif !defined _WIN32 && !defined __linux__ && !defined __APPLE__ && !defined __ANDROID__ && !defined __unix__ && !defined __arm__ // Greetings, Outlander! ;) #else // Generic handling #endif
sumber