Di mana saya harus memilih menggunakan makro dan di mana saya harus memilih constexpr ? Bukankah pada dasarnya mereka sama?
#define MAX_HEIGHT 720
vs.
constexpr unsigned int max_height = 720;
Di mana saya harus memilih menggunakan makro dan di mana saya harus memilih constexpr ? Bukankah pada dasarnya mereka sama?
#define MAX_HEIGHT 720
vs.
constexpr unsigned int max_height = 720;
Jawaban:
Bukankah pada dasarnya mereka sama?
Tidak. Sama sekali tidak. Bahkan tidak dekat.
Terlepas dari kenyataan makro Anda adalah int
dan Anda constexpr unsigned
adalah unsigned
, ada perbedaan penting dan macro hanya memiliki satu keuntungan.
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 nama MAX_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 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 #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?
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 panggilan std::max
sementara int
harus dibuat oleh kompilator. Referensi yang dikembalikan oleh std::max
mungkin kemudian merujuk ke sementara itu, yang tidak ada setelah akhir pernyataan itu, jadi return 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.)
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>;
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 sepele constexpr
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 kembali std::max
. 2. ya, itu sebabnya tidak mengembalikan referensi. 3. salah lihat link coliru diatas.
const int& h = max(x, y);
dan max
mengembalikan dengan nilai seumur hidup nilai pengembaliannya diperpanjang. Bukan oleh tipe kembalian, tapi oleh tipe const 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.
Makro adalah pengganti sederhana dalam kode, dan karena alasan ini, max
makro 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_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.
#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
dan constexpr
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 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