const statis vs #define


212

Apakah lebih baik menggunakan static constvars daripada #definepreprosesor? Atau mungkin itu tergantung pada konteksnya?

Apa kelebihan / kekurangan untuk setiap metode?


14
Scott Meyers membahas masalah ini dengan sangat baik dan menyeluruh. Item-nya # 2 dalam "Efektif C ++ Edisi Ketiga". Dua kasus khusus (1) konstanta statis lebih disukai dalam ruang lingkup kelas untuk konstanta spesifik kelas; (2) namespace atau const lingkup anonim lebih disukai daripada #define.
Eric

2
Saya lebih suka Enums. Karena itu adalah gabungan keduanya. Tidak menempati ruang kecuali Anda membuat variabel. Jika Anda hanya ingin menggunakan konstanta, enum adalah pilihan terbaik. Ini memiliki tipe keamanan di C / C ++ 11 std dan juga konstanta yang sempurna. #define adalah tipe tidak aman, const membutuhkan ruang jika kompiler tidak dapat mengoptimalkannya.
siddhusingh

1
Keputusan saya apakah akan menggunakan #defineatau static const(untuk string) didorong oleh aspek inisialisasi (tidak disebutkan melalui jawaban di bawah): jika konstanta hanya digunakan dalam unit kompilasi tertentu saja, maka saya akan pergi dengan static const, yang lain saya gunakan #define- hindari kegagalan inisialisasi urutan statis. isocpp.org/wiki/faq/ctors#static-init-order
Martin Dvorak

Jika const, constexpratau enumvariasi apa pun berfungsi dalam kasus Anda, maka pilih itu#define
Phil1970

@ MartinDvorak " hindari kegagalan inisialisasi urutan statis " Bagaimana masalah untuk konstanta?
curiousguy

Jawaban:


139

Secara pribadi, saya benci preprocessor, jadi saya akan selalu pergi dengan const.

Keuntungan utama dari a #defineadalah tidak memerlukan memori untuk disimpan dalam program Anda, karena sebenarnya hanya mengganti beberapa teks dengan nilai literal. Ini juga memiliki keuntungan karena tidak memiliki tipe, sehingga dapat digunakan untuk nilai integer apa pun tanpa menghasilkan peringatan.

Keuntungan dari " const" adalah bahwa mereka dapat dicakup, dan mereka dapat digunakan dalam situasi di mana penunjuk ke objek perlu dilewatkan.

Saya tidak tahu persis apa yang Anda maksud dengan bagian " static" itu. Jika Anda mendeklarasikan secara global, saya akan meletakkannya di namespace anonim alih-alih menggunakan static. Sebagai contoh

namespace {
   unsigned const seconds_per_minute = 60;
};

int main (int argc; char *argv[]) {
...
}

8
Konstanta string secara khusus adalah salah satu yang mungkin mendapat manfaat dari menjadi #defined, setidaknya jika mereka dapat digunakan sebagai "blok bangunan" untuk konstanta string yang lebih besar. Lihat balasan saya untuk contoh.
AnT

62
The #definekeuntungan tidak menggunakan memori setiap tidak akurat. "60" pada contoh harus disimpan di suatu tempat, terlepas dari apakah itu static constatau #define. Bahkan, saya telah melihat kompiler di mana menggunakan #define menyebabkan konsumsi memori besar (hanya baca), dan const statis tidak menggunakan memori yang tidak diperlukan.
Gilad Naor

3
#Define seperti jika Anda mengetiknya, jadi pasti bukan dari memori.
Yang Terhormat

27
@theReverend Apakah nilai literal entah bagaimana dikecualikan dari konsumsi sumber daya mesin? Tidak, mereka mungkin menggunakannya dengan cara yang berbeda, mungkin itu tidak akan muncul di stack atau heap, tetapi di beberapa titik program dimuat ke memori bersama dengan semua nilai yang dikompilasi ke dalamnya.
Sqeaky

13
@ gilad-naor, Anda benar secara umum tetapi bilangan bulat kecil seperti 60 sebenarnya terkadang menjadi semacam pengecualian parsial. Beberapa set instruksi memiliki kemampuan untuk menyandikan bilangan bulat atau subset bilangan bulat langsung dalam aliran instruksi. Sebagai contoh, MIP segera ditambahkan ( cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/addi.html ). Dalam kasus seperti ini integer #defined benar-benar dapat dikatakan tidak menggunakan ruang karena dalam biner yang dikompilasinya ia menempati beberapa bit cadangan dalam instruksi yang harus tetap ada.
ahcox

242

Pro dan kontra antara #defines, consts dan (apa yang Anda lupa) enum, tergantung penggunaan:

  1. enums:

    • hanya mungkin untuk nilai integer
    • masalah benturan enum class Xdengan cakupan / pengidentifikasi yang ditangani dengan baik, khususnya di kelas C ++ 11 enum di mana enumerasi untuk disatukan oleh ruang lingkupX::
    • sangat diketik, tetapi ke ukuran int cukup-masuk-atau-tidak-ditandatangani di mana Anda tidak memiliki kontrol di C ++ 03 (meskipun Anda dapat menentukan bidang bit di mana mereka harus dikemas jika enum adalah anggota struct / class / union), sedangkan C ++ 11 default inttetapi dapat secara eksplisit diatur oleh programmer
    • tidak dapat mengambil alamat - tidak ada karena nilai enumerasi secara efektif diganti secara inline pada titik penggunaan
    • pembatasan penggunaan yang lebih kuat (misalnya penambahan - template <typename T> void f(T t) { cout << ++t; }tidak akan dikompilasi, meskipun Anda dapat membungkus enum ke dalam kelas dengan konstruktor implisit, operator casting dan operator yang ditentukan pengguna)
    • setiap tipe konstanta diambil dari enum penutup, jadi template <typename T> void f(T)dapatkan instantiasi yang berbeda ketika melewati nilai numerik yang sama dari enum yang berbeda, yang semuanya berbeda dari f(int)instantiasi yang sebenarnya . Kode objek masing-masing fungsi bisa identik (mengabaikan offset alamat), tapi saya tidak akan mengharapkan kompiler / penghubung untuk menghilangkan salinan yang tidak perlu, meskipun Anda dapat memeriksa kompiler / penghubung Anda jika Anda peduli.
    • bahkan dengan typeof / decltype, tidak dapat mengharapkan numeric_limits untuk memberikan wawasan yang berguna ke dalam set nilai dan kombinasi yang bermakna (memang, kombinasi "legal" bahkan tidak dinotasikan dalam kode sumber, anggap enum { A = 1, B = 2 }- adalah A|B"legal" dari logika program perspektif?)
    • nama ketik enum dapat muncul di berbagai tempat di RTTI, pesan kompiler dll. - mungkin berguna, mungkin kebingungan
    • Anda tidak dapat menggunakan enumerasi tanpa unit terjemahan benar-benar melihat nilai, yang berarti enum di API perpustakaan membutuhkan nilai yang diekspos di header, dan makedan alat rekompilasi berbasis timestamp lainnya akan memicu kompilasi ulang klien ketika mereka diubah (buruk! )

  1. consts:

    • masalah benturan dengan cakupan / pengidentifikasi yang ditangani dengan baik
    • kuat, tunggal, tipe yang ditentukan pengguna
      • Anda mungkin mencoba untuk "mengetik" #defineala #define S std::string("abc"), tetapi konstanta menghindari pembangunan berulang temporaries yang berbeda pada setiap titik penggunaan
    • Satu Definisi Aturan komplikasi
    • dapat mengambil alamat, membuat referensi const untuk mereka dll.
    • paling mirip dengan yang tidak constbernilai, yang meminimalkan pekerjaan dan dampak jika beralih di antara keduanya
    • nilai dapat ditempatkan di dalam file implementasi, memungkinkan kompilasi ulang lokal dan hanya tautan klien untuk mengambil perubahan

  1. #defines:

    • Ruang lingkup "global" / lebih rentan terhadap penggunaan yang saling bertentangan, yang dapat menghasilkan masalah kompilasi yang sulit diselesaikan dan hasil run-time yang tidak terduga daripada pesan kesalahan yang waras; mengurangi ini membutuhkan:
      • pengidentifikasi yang panjang, tidak jelas, dan / atau terkoordinasi secara terpusat, dan akses ke mereka tidak dapat mengambil manfaat dari pencocokan secara tersirat namespace bekas / saat ini / Koenig-look-up, alias namespace dll.
      • sementara praktik terbaik trumping memungkinkan pengidentifikasi parameter templat menjadi huruf besar karakter tunggal (mungkin diikuti oleh angka), penggunaan pengidentifikasi lainnya tanpa huruf kecil secara konvensional disediakan untuk dan diharapkan dari definisi preprosesor (di luar perpustakaan OS dan C / C ++) header). Ini penting agar penggunaan preprosesor skala perusahaan tetap terkelola. Perpustakaan pihak ketiga dapat diharapkan untuk mematuhi. Mengamati hal ini menyiratkan migrasi konst atau enum yang ada ke / dari definisi melibatkan perubahan dalam kapitalisasi, dan karenanya memerlukan pengeditan kode sumber klien daripada kompilasi ulang "sederhana". (Secara pribadi, saya menggunakan huruf besar dari huruf pertama dari pencacahan tetapi bukan konstanta, jadi saya juga akan terpukul untuk bermigrasi di antara keduanya - mungkin waktu untuk memikirkan kembali hal itu.)
    • operasi waktu kompilasi yang lebih mungkin: string literal string, stringifikasi (mengambil ukurannya), concatenation menjadi pengidentifikasi
      • Kelemahannya adalah bahwa diberikan #define X "x"dan beberapa penggunaan klien ala "pre" X "post", jika Anda ingin atau perlu membuat X variabel runtime-berubah daripada konstan Anda memaksa pengeditan ke kode klien (bukan hanya kompilasi ulang), sedangkan transisi yang lebih mudah dari const char*atau const std::stringdiberikan mereka sudah memaksa pengguna untuk memasukkan operasi gabungan (misalnya "pre" + X + "post"untuk string)
    • tidak dapat digunakan sizeofsecara langsung pada literal numerik yang ditentukan
    • untyped (GCC tidak memperingatkan jika dibandingkan dengan unsigned)
    • beberapa kompiler / penghubung / rantai debugger mungkin tidak menampilkan pengidentifikasi, jadi Anda akan direduksi menjadi melihat "angka ajaib" (string, apa pun ...)
    • tidak dapat mengambil alamat
    • nilai yang diganti tidak boleh legal (atau diskrit) dalam konteks di mana #define dibuat, seperti yang dievaluasi pada setiap titik penggunaan, sehingga Anda dapat mereferensikan objek yang belum dideklarasikan, bergantung pada "implementasi" yang tidak perlu dimasukkan terlebih dahulu, buat "konstanta" seperti { 1, 2 }yang dapat digunakan untuk menginisialisasi array, atau lainnya #define MICROSECONDS *1E-6( pasti tidak merekomendasikan ini!)
    • beberapa hal khusus seperti __FILE__dan __LINE__dapat dimasukkan ke dalam substitusi makro
    • Anda dapat menguji keberadaan dan nilai dalam #ifpernyataan untuk menyertakan kode secara kondisional (lebih kuat daripada "jika" pasca-preproses karena kode tidak perlu dikompilasi jika tidak dipilih oleh preprocessor), gunakan #undef-ine, redefine dll.
    • teks yang diganti harus dibuka:
      • di unit terjemahan yang digunakan olehnya, yang berarti makro di perpustakaan untuk penggunaan klien harus ada di header, jadi makedan alat rekompilasi berbasis timestamp lainnya akan memicu kompilasi ulang klien saat diubah (buruk!)
      • atau pada baris perintah, di mana bahkan lebih hati-hati diperlukan untuk memastikan kode klien dikompilasi ulang (mis. Makefile atau skrip yang memasok definisi harus didaftar sebagai ketergantungan)

Pendapat pribadi saya:

Sebagai aturan umum, saya menggunakan consts dan menganggapnya sebagai opsi paling profesional untuk penggunaan umum (meskipun yang lain memiliki kesederhanaan yang menarik bagi pemrogram malas yang lama ini).


1
Jawaban yang luar biasa. Satu nit kecil: saya kadang-kadang menggunakan enum lokal yang tidak di header sama sekali hanya untuk kejelasan kode, seperti di mesin negara kecil dan semacamnya. Jadi mereka tidak harus berada di header, setiap saat.
kert

Pro dan kontra digabungkan, saya akan sangat suka melihat tabel perbandingan.
Unknown123

@ Tidak Diketahui123: jangan ragu untuk memposting satu - Saya tidak keberatan jika Anda merobek poin yang Anda rasa layak dari sini. Cheers
Tony Delroy

48

Jika ini adalah pertanyaan C ++ dan ia menyebutkan #definesebagai alternatif, maka ini adalah tentang konstanta "global" (yaitu file-scope), bukan tentang anggota kelas. Ketika datang ke konstanta seperti itu di C ++ static constadalah redundan. Dalam C ++ constmemiliki tautan internal secara default dan tidak ada gunanya mendeklarasikannya static. Jadi itu benar-benar tentang constvs #define.

Dan, akhirnya, di C ++ constlebih disukai. Setidaknya karena konstanta seperti itu diketik dan dicakup. Tidak hanya ada alasan untuk lebih #definelebih const, selain dari beberapa pengecualian.

Konstanta string, BTW, adalah salah satu contoh pengecualian tersebut. Dengan #definekonstanta d string seseorang dapat menggunakan fitur penggabungan waktu kompilasi dari kompiler C / C ++, seperti pada

#define OUT_NAME "output"
#define LOG_EXT ".log"
#define TEXT_EXT ".txt"

const char *const log_file_name = OUT_NAME LOG_EXT;
const char *const text_file_name = OUT_NAME TEXT_EXT;

PS Sekali lagi, untuk berjaga-jaga, ketika seseorang menyebutkan static constsebagai alternatif #define, biasanya itu berarti mereka berbicara tentang C, bukan tentang C ++. Saya ingin tahu apakah pertanyaan ini ditandai dengan benar ...


1
"sama sekali tidak ada alasan untuk lebih memilih #define " Atas apa? Variabel statis didefinisikan dalam file header?
curiousguy

9

#define dapat menyebabkan hasil yang tidak terduga:

#include <iostream>

#define x 500
#define y x + 5

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

Menghasilkan hasil yang salah:

y is 505
z is 510

Namun, jika Anda mengganti ini dengan konstanta:

#include <iostream>

const int x = 500;
const int y = x + 5;

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

Ini menghasilkan hasil yang benar:

y is 505
z is 1010

Ini karena #definehanya mengganti teks. Karena melakukan ini secara serius dapat mengacaukan urutan operasi, saya akan merekomendasikan menggunakan variabel konstan sebagai gantinya.


1
Saya memiliki hasil yang berbeda yang tidak terduga: ymemiliki nilai 5500, gabungan sedikit-endian dari xdan 5.
Kode dengan Hammer

5

Menggunakan const statis sama seperti menggunakan variabel const lainnya dalam kode Anda. Ini berarti Anda dapat melacak dari mana pun informasi itu berasal, bukan dari #define yang hanya akan diganti dalam kode dalam proses pra-kompilasi.

Anda mungkin ingin melihat C ++ FAQ Lite untuk pertanyaan ini: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7


4
  • Const statis diketik (memiliki tipe) dan dapat diperiksa oleh kompiler untuk validitas, redefinisi, dll.
  • #define dapat didefinisikan ulang, tidak ditentukan apa pun.

Biasanya Anda harus lebih suka const statis. Itu tidak memiliki kerugian. Prosesor terutama harus digunakan untuk kompilasi bersyarat (dan kadang-kadang untuk trics yang sangat kotor mungkin).


3

Menentukan konstanta dengan menggunakan arahan preprocessor #definetidak disarankan untuk diterapkan tidak hanya dalam C++, tetapi juga dalam C. Konstanta ini tidak akan memiliki tipe. Bahkan dalam Cdiusulkan untuk digunakan constuntuk konstanta.



2

Selalu lebih suka menggunakan fitur bahasa daripada beberapa alat tambahan seperti preprosesor.

ES.31: Jangan gunakan makro untuk konstanta atau "fungsi"

Macro adalah sumber utama bug. Makro tidak mematuhi lingkup dan jenis aturan yang biasa. Macro tidak menaati aturan yang biasa untuk melewati argumen. Macro memastikan bahwa pembaca manusia melihat sesuatu yang berbeda dari apa yang dilihat oleh kompiler. Makro mempersulit pembuatan alat.

Dari C ++ Core Guidelines


0

Jika Anda mendefinisikan konstanta untuk dibagikan di antara semua instance kelas, gunakan konstanta statis. Jika konstanta spesifik untuk setiap instance, cukup gunakan const (tetapi perhatikan bahwa semua konstruktor dari kelas harus menginisialisasi variabel anggota const ini dalam daftar inisialisasi).

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.