Dalam proyek C ++ yang sedang saya kerjakan, saya memiliki semacam flag nilai yang dapat memiliki empat nilai. Keempat bendera itu bisa digabungkan. Bendera menggambarkan rekaman dalam database dan dapat:
- rekor baru
- catatan dihapus
- catatan yang dimodifikasi
- catatan yang ada
Sekarang, untuk setiap catatan saya ingin menyimpan atribut ini, jadi saya bisa menggunakan enum:
enum { xNew, xDeleted, xModified, xExisting }
Namun, di tempat lain dalam kode, saya harus memilih catatan mana yang dapat dilihat oleh pengguna, jadi saya ingin dapat meneruskannya sebagai parameter tunggal, seperti:
showRecords(xNew | xDeleted);
Jadi, sepertinya saya punya tiga kemungkinan appoaches:
#define X_NEW 0x01
#define X_DELETED 0x02
#define X_MODIFIED 0x04
#define X_EXISTING 0x08
atau
typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;
atau
namespace RecordType {
static const uint8 xNew = 1;
static const uint8 xDeleted = 2;
static const uint8 xModified = 4;
static const uint8 xExisting = 8;
}
Persyaratan ruang penting (byte vs int) tetapi tidak penting. Dengan mendefinisikan saya kehilangan keamanan jenis, dan dengan enum
saya kehilangan beberapa ruang (bilangan bulat) dan mungkin harus dilemparkan ketika saya ingin melakukan operasi bitwise. Dengan const
saya pikir saya juga kehilangan keamanan jenis karena acak uint8
bisa masuk secara tidak sengaja.
Apakah ada cara lain yang lebih bersih?
Jika tidak, apa yang akan Anda gunakan dan mengapa?
PS Sisa kode C ++ modern agak bersih tanpa #define
s, dan saya telah menggunakan ruang nama dan templat dalam beberapa ruang, jadi itu juga tidak diragukan.
sumber
enum RecordType : uint8_t
gabungkan keamanan jenisenum
dengan ukuran keciluint8_t
, meskipun Anda masih harus menyediakan operator bitwise.Jawaban:
Gabungkan strategi untuk mengurangi kerugian dari satu pendekatan. Saya bekerja di sistem tertanam sehingga solusi berikut didasarkan pada kenyataan bahwa operator integer dan bitwise cepat, memori rendah & rendah dalam penggunaan flash.
Tempatkan enum di namespace untuk mencegah konstanta mencemari namespace global.
Enum menyatakan dan mendefinisikan waktu kompilasi yang diketik yang diketik. Selalu gunakan pengecekan tipe waktu kompilasi untuk memastikan argumen dan variabel diberi tipe yang benar. Tidak perlu untuk typedef di C ++.
Buat anggota lain untuk status yang tidak valid. Ini dapat berguna sebagai kode kesalahan; misalnya, ketika Anda ingin mengembalikan negara tetapi operasi I / O gagal. Ini juga berguna untuk debugging; gunakan dalam daftar inisialisasi dan destruktor untuk mengetahui apakah nilai variabel harus digunakan.
Pertimbangkan bahwa Anda memiliki dua tujuan untuk jenis ini. Untuk melacak status saat ini dari catatan dan untuk membuat topeng untuk memilih catatan di negara-negara tertentu. Buat fungsi sebaris untuk menguji apakah nilai jenis ini valid untuk tujuan Anda; sebagai penanda negara vs topeng negara. Ini akan menangkap bug karena
typedef
hanya merupakanint
dan nilai seperti0xDEADBEEF
mungkin ada dalam variabel Anda melalui variabel yang tidak diinisialisasi atau salah penempatan.Tambahkan
using
arahan jika Anda ingin sering menggunakan tipe itu.Fungsi pemeriksaan nilai berguna dalam menegaskan untuk menjebak nilai buruk segera setelah digunakan. Semakin cepat Anda menangkap bug saat berjalan, semakin sedikit kerusakan yang dapat dilakukannya.
Berikut adalah beberapa contoh untuk menyatukan semuanya.
Satu-satunya cara untuk memastikan keamanan nilai yang benar adalah dengan menggunakan kelas khusus dengan kelebihan operator dan dibiarkan sebagai latihan untuk pembaca lain.
sumber
IsValidMask
tidak mengizinkan memilih tidak ada (yaitu0
)?Lupakan definisi
Mereka akan mencemari kode Anda.
bitfields?
Jangan pernah gunakan itu . Anda lebih mementingkan kecepatan daripada menghemat 4 int. Menggunakan bidang bit sebenarnya lebih lambat dari akses ke jenis lainnya.
Sumber: http://en.wikipedia.org/wiki/Bit_field :
Dan jika Anda memerlukan lebih banyak alasan untuk tidak menggunakan bitfield, mungkin Raymond Chen akan meyakinkan Anda dalam bukunya The Old New Thing Post: Analisis biaya-manfaat bitfields untuk koleksi boolean di http://blogs.msdn.com/oldnewthing/ arsip / 2008/11/26 / 9143050.aspx
const int?
Menempatkan mereka di namespace itu keren. Jika mereka dideklarasikan dalam file CPP atau header Anda, nilainya akan diuraikan. Anda dapat menggunakan sakelar pada nilai-nilai itu, tetapi akan sedikit meningkatkan kopling.
Ah, ya: hapus kata kunci statis . statis ditinggalkan dalam C ++ ketika digunakan seperti yang Anda lakukan, dan jika uint8 adalah tipe buildin, Anda tidak akan memerlukan ini untuk mendeklarasikan ini di header yang disertakan oleh banyak sumber dari modul yang sama. Pada akhirnya, kodenya harus:
Masalah dari pendekatan ini adalah bahwa kode Anda mengetahui nilai konstanta Anda, yang sedikit meningkatkan kopling.
enum
Sama seperti const int, dengan pengetikan yang agak kuat.
Mereka masih mencemari namespace global. Omong-omong ... Hapus typedef . Anda bekerja di C ++. Jenis-jenis enum dan struct itu mencemari kode lebih dari apa pun.
Hasilnya agak:
Seperti yang Anda lihat, enum Anda mencemari namespace global. Jika Anda meletakkan enum ini di namespace, Anda akan memiliki sesuatu seperti:
extern const int?
Jika Anda ingin mengurangi kopling (yaitu dapat menyembunyikan nilai konstanta, dan karenanya, memodifikasinya seperti yang diinginkan tanpa memerlukan kompilasi ulang penuh), Anda dapat mendeklarasikan int sebagai extern di header, dan sebagai konstanta pada file CPP , seperti pada contoh berikut:
Dan:
Anda tidak akan dapat menggunakan sakelar pada konstanta itu. Jadi pada akhirnya, pilih racunmu ... :-p
sumber
Sudahkah Anda mengesampingkan std :: bitset? Set bendera adalah untuk apa itu. Melakukan
kemudian
Karena ada banyak operator kelebihan bit, Anda sekarang dapat melakukannya
Atau sesuatu yang sangat mirip dengan itu - saya menghargai koreksi karena saya belum menguji ini. Anda juga bisa merujuk ke bit berdasarkan indeks, tetapi umumnya terbaik untuk mendefinisikan hanya satu set konstanta, dan konstanta RecordType mungkin lebih berguna.
Dengan asumsi Anda telah mengesampingkan bitset, saya memilih enum .
Saya tidak membeli bahwa casting enum adalah kerugian serius - OK jadi agak bising, dan menetapkan nilai di luar kisaran untuk enum adalah perilaku yang tidak terdefinisi sehingga secara teori dimungkinkan untuk menembak diri sendiri dengan kaki pada beberapa C ++ yang tidak biasa implementasi. Tetapi jika Anda hanya melakukannya bila perlu (yaitu saat beralih dari int ke enum iirc), itu adalah kode normal yang sudah pernah dilihat orang sebelumnya.
Saya ragu tentang biaya ruang enum juga. variabel dan parameter uint8 mungkin tidak akan menggunakan tumpukan yang kurang dari int, jadi hanya penyimpanan di kelas yang penting. Ada beberapa kasus di mana pengemasan beberapa byte dalam sebuah struct akan menang (dalam hal ini Anda dapat memasukkan enum ke dalam dan keluar dari penyimpanan uint8), tetapi biasanya bantalan akan mematikan manfaatnya.
Jadi enum tidak memiliki kerugian dibandingkan dengan yang lain, dan sebagai keuntungan memberi Anda sedikit jenis-keamanan (Anda tidak dapat menetapkan beberapa nilai integer acak tanpa secara eksplisit melakukan casting) dan cara bersih untuk merujuk ke semuanya.
Untuk preferensi saya juga meletakkan "= 2" di enum, omong-omong. Itu tidak perlu, tetapi "prinsip paling tidak heran" menunjukkan bahwa semua 4 definisi harus terlihat sama.
sumber
bitset
ini? biasanya diterjemahkan menjadilong
(dalam implementasi saya iirc; ya, betapa borosnya) atau tipe integral yang serupa untuk setiap elemen, jadi mengapa tidak hanya menggunakan integral yang tidak dikobarkan? (atau, saat ini,constexpr
dengan penyimpanan nol)bitset
kelas, selain dari apa yang tampaknya menjadi arus bawah berulang dalam diskusi sekitar 'ugh, kita harus menutupi akar tingkat rendah yang tidak sopan dari bahasa 'uint8
variabel dan parameter mungkin tidak akan menggunakan tumpukan kurang dariints
" salah. Jika Anda memiliki CPU dengan register 8-Bit,int
perlu setidaknya 2 register sementarauint8_t
hanya membutuhkan 1, sehingga Anda akan memerlukan lebih banyak ruang tumpukan karena Anda lebih cenderung keluar dari register (yang juga lebih lambat dan dapat meningkatkan ukuran kode ( tergantung pada set instruksi)). (Anda memiliki tipe, seharusnyauint8_t
tidakuint8
)Berikut adalah beberapa artikel tentang const vs macro vs enums:
Simbol Konstanta
Enumerasi Konstanta vs Obyek Konstan
Saya pikir Anda harus menghindari macro terutama karena Anda menulis sebagian besar kode baru Anda di C ++ modern.
sumber
Jika mungkin, JANGAN gunakan makro. Mereka tidak terlalu dikagumi ketika datang ke C ++ modern.
sumber
Enum akan lebih tepat karena mereka memberikan "makna bagi pengidentifikasi" serta keamanan jenis. Anda dapat dengan jelas mengatakan bahwa "xDeleted" adalah dari "RecordType" dan yang mewakili "jenis catatan" (wow!) Bahkan setelah bertahun-tahun. Const akan membutuhkan komentar untuk itu, juga mereka akan perlu naik turun kode.
sumber
Belum tentu...
Tidak harus - tetapi Anda harus eksplisit di titik penyimpanan ...
Anda dapat membuat operator untuk menghilangkan rasa sakit itu:
Hal yang sama dapat terjadi dengan salah satu dari mekanisme ini: kisaran dan pemeriksaan nilai biasanya ortogonal untuk mengetik keselamatan (meskipun tipe yang ditentukan pengguna - yaitu kelas Anda sendiri - dapat memberlakukan "invarian" tentang data mereka). Dengan enum, kompiler bebas untuk memilih tipe yang lebih besar untuk meng-host nilai-nilai, dan variabel enum yang tidak diinisialisasi, rusak, atau salah-set masih dapat berakhir menginterpretasikan pola bitnya sebagai angka yang tidak Anda harapkan - membandingkan tidak setara dengan salah satu dari pengidentifikasi enumerasi, kombinasi mereka, dan 0.
Yah, pada akhirnya bitwise C-style yang dicoba dan dipercaya ATAU dari enumerasi bekerja dengan baik begitu Anda memiliki bidang bit dan operator khusus dalam gambar. Anda selanjutnya dapat meningkatkan ketahanan Anda dengan beberapa fungsi validasi dan pernyataan seperti pada jawaban mat_geek; teknik yang sering digunakan untuk menangani string, int, nilai ganda dll.
Anda dapat berargumen bahwa ini "bersih":
Saya acuh tak acuh: bit data pak lebih ketat tetapi kode tumbuh secara signifikan ... tergantung berapa banyak objek yang Anda punya, dan lamdbas - seindah mereka - masih berantakan dan lebih sulit untuk diperbaiki daripada bitwise OR.
BTW / - argumen tentang IMHO keselamatan thread yang cukup lemah - paling baik diingat sebagai pertimbangan latar belakang daripada menjadi kekuatan pendorong keputusan yang dominan; berbagi mutex melintasi bitfield adalah praktik yang lebih mungkin bahkan jika tidak menyadari pengemasan mereka (mutex adalah anggota data yang relatif besar - saya harus benar-benar khawatir tentang kinerja untuk mempertimbangkan memiliki beberapa mutex pada anggota satu objek, dan saya akan melihat dengan hati-hati cukup untuk melihat mereka adalah bidang bit). Setiap jenis sub-kata dapat memiliki masalah yang sama (misalnya a
uint8_t
). Ngomong-ngomong, Anda bisa mencoba operasi gaya bandingkan-dan-tukar atom jika Anda putus asa untuk konkurensi yang lebih tinggi.sumber
operator|
harus dilemparkan ke tipe integer (unsigned int
) sebelum instruksi|
. Jika tidak makaoperator|
akan memanggil dirinya sendiri secara berulang dan menyebabkan stack run-time overflow. Saya sarankan:return RecordType( unsigned(lhs) | unsigned(rhs) );
. CheersBahkan jika Anda harus menggunakan 4 byte untuk menyimpan enum (Saya tidak terlalu familiar dengan C ++ - Saya tahu Anda dapat menentukan tipe yang mendasarinya di C #), itu masih layak - gunakan enums.
Pada hari ini dan usia server dengan memori GB, hal-hal seperti 4 byte vs 1 byte memori pada level aplikasi secara umum tidak masalah. Tentu saja, jika dalam situasi khusus Anda, penggunaan memori sangat penting (dan Anda tidak bisa mendapatkan C ++ untuk menggunakan byte untuk mendukung enum), maka Anda dapat mempertimbangkan rute 'static const'.
Pada akhirnya, Anda harus bertanya pada diri sendiri, apakah itu layak untuk pemeliharaan dengan menggunakan 'static const' untuk penghematan memori 3 byte untuk struktur data Anda?
Hal lain yang perlu diingat - IIRC, pada x86, struktur data sejajar 4-byte, jadi kecuali Anda memiliki sejumlah elemen selebar-byte dalam struktur 'record' Anda, itu mungkin tidak terlalu penting. Uji dan pastikan itu dilakukan sebelum Anda melakukan tradeoff dalam pemeliharaan untuk kinerja / ruang.
sumber
int
kecuali itu terlalu kecil". [Jika Anda tidak menentukan tipe yang mendasarinya dalam C ++ 11, itu menggunakan perilaku lama. Sebaliknya,enum class
tipe dasar C ++ 11 secara eksplisit default keint
jika tidak ditentukan.]Jika Anda menginginkan jenis keamanan kelas, dengan kenyamanan sintaks enumerasi dan pemeriksaan bit, pertimbangkan Label Aman di C ++ . Saya telah bekerja dengan penulis, dan dia cukup pintar.
Namun berhati-hatilah. Pada akhirnya, paket ini menggunakan templat dan makro!
sumber
Apakah Anda benar-benar perlu memberikan nilai flag sebagai keseluruhan konseptual, atau Anda akan memiliki banyak kode per-bendera? Either way, saya pikir memiliki ini sebagai kelas atau struct dari bitfield 1-bit mungkin sebenarnya lebih jelas:
Kemudian kelas catatan Anda dapat memiliki variabel anggota RecordFruktur struct, fungsi dapat mengambil argumen dari tipe struct RecordFlag, dll. Kompiler harus mengemas bitfields bersama-sama, menghemat ruang.
sumber
Saya mungkin tidak akan menggunakan enum untuk hal semacam ini di mana nilai-nilai dapat digabungkan bersama, lebih biasanya enum adalah keadaan yang saling eksklusif.
Tetapi metode apa pun yang Anda gunakan, untuk membuatnya lebih jelas bahwa ini adalah nilai yang merupakan bit yang dapat digabungkan bersama, gunakan sintaks ini untuk nilai aktual sebagai gantinya:
Menggunakan shift kiri ada membantu untuk menunjukkan bahwa setiap nilai dimaksudkan untuk menjadi bit tunggal, kecil kemungkinannya bahwa nanti seseorang akan melakukan sesuatu yang salah seperti menambahkan nilai baru dan menetapkannya sesuatu nilai 9.
sumber
Berdasarkan KISS , kohesi tinggi dan kopling rendah , ajukan pertanyaan ini -
Ada buku bagus " Desain Perangkat Lunak C ++ Skala Besar ", ini mempromosikan tipe dasar secara eksternal, jika Anda dapat menghindari ketergantungan file header / antarmuka lain yang harus Anda coba.
sumber
Jika Anda menggunakan Qt, Anda harus mencari QFlags . Kelas QFlags menyediakan cara yang aman untuk menyimpan kombinasi AT atau nilai enum.
sumber
Saya lebih suka pergi dengan
Hanya karena:
sumber
Bukannya saya suka merekayasa semuanya secara berlebihan, tetapi kadang-kadang dalam kasus ini mungkin perlu membuat kelas (kecil) untuk merangkum informasi ini. Jika Anda membuat RecordType kelas maka mungkin memiliki fungsi seperti:
membatalkan setDeleted ();
batal clearDeleted ();
bool isDeleted ();
dll ... (atau apa pun yang sesuai dengan konvensi)
Itu dapat memvalidasi kombinasi (dalam kasus di mana tidak semua kombinasi legal, misalnya jika 'baru' dan 'dihapus' tidak dapat keduanya diatur pada waktu yang sama). Jika Anda hanya menggunakan bit mask dll, maka kode yang menetapkan status yang perlu divalidasi, suatu kelas dapat merangkum logika itu juga.
Kelas juga dapat memberi Anda kemampuan untuk melampirkan info logging yang bermakna ke setiap negara, Anda dapat menambahkan fungsi untuk mengembalikan representasi string dari keadaan saat ini, dll (atau menggunakan operator streaming '<<').
Untuk semua itu jika Anda khawatir tentang penyimpanan Anda masih bisa memiliki kelas hanya memiliki anggota data 'char', jadi hanya mengambil sejumlah kecil penyimpanan (dengan asumsi itu bukan virtual). Tentu saja tergantung pada perangkat keras dll Anda mungkin memiliki masalah penyelarasan.
Anda bisa memiliki nilai bit aktual yang tidak terlihat oleh sisa 'dunia' jika mereka berada dalam ruang nama anonim di dalam file cpp daripada di file header.
Jika Anda menemukan bahwa kode yang menggunakan enum / # define / bitmask dll memiliki banyak kode 'dukungan' untuk menangani kombinasi yang tidak valid, masuk dll, maka enkapsulasi dalam suatu kelas mungkin perlu dipertimbangkan. Tentu saja sebagian besar masalah sederhana lebih baik dengan solusi sederhana ...
sumber