Haruskah saya menggunakan #define, enum atau const?


125

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 enumsaya kehilangan beberapa ruang (bilangan bulat) dan mungkin harus dilemparkan ketika saya ingin melakukan operasi bitwise. Dengan constsaya pikir saya juga kehilangan keamanan jenis karena acak uint8bisa 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 #defines, dan saya telah menggunakan ruang nama dan templat dalam beberapa ruang, jadi itu juga tidak diragukan.


"dengan enum saya kehilangan beberapa ruang (bilangan bulat)". Belum tentu. Lihat stackoverflow.com/questions/366017/… dan stackoverflow.com/questions/1113855/… (dan gcc's -fshort-enum . (Saya berasumsi jawaban C ini masih benar di C ++.)
idbrii

@ pydave Jika Anda tidak yakin tentang kompatibilitas C dan C ++, saya menemukan tautan ini sangat membantu, lihat misalnya enum david.tribble.com/text/cdiffs.htm#C99-enum-type
aka.nice

3
Ini adalah topik lama dengan suara tinggi, adakah alasan untuk tidak menyebutkan kelas C ++ 11 enum untuk situasi masalah ini.
Brandin

Sebagai catatan, enum RecordType : uint8_tgabungkan keamanan jenis enumdengan ukuran kecil uint8_t, meskipun Anda masih harus menyediakan operator bitwise.
Justin Time - Reinstate Monica

Jawaban:


88

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.

namespace RecordType {

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 ++.

enum TRecordType { xNew = 1, xDeleted = 2, xModified = 4, xExisting = 8,

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.

xInvalid = 16 };

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 typedefhanya merupakan intdan nilai seperti 0xDEADBEEFmungkin ada dalam variabel Anda melalui variabel yang tidak diinisialisasi atau salah penempatan.

inline bool IsValidState( TRecordType v) {
    switch(v) { case xNew: case xDeleted: case xModified: case xExisting: return true; }
    return false;
}

 inline bool IsValidMask( TRecordType v) {
    return v >= xNew  && v < xInvalid ;
}

Tambahkan usingarahan jika Anda ingin sering menggunakan tipe itu.

using RecordType ::TRecordType ;

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.

void showRecords(TRecordType mask) {
    assert(RecordType::IsValidMask(mask));
    // do stuff;
}

void wombleRecord(TRecord rec, TRecordType state) {
    assert(RecordType::IsValidState(state));
    if (RecordType ::xNew) {
    // ...
} in runtime

TRecordType updateRecord(TRecord rec, TRecordType newstate) {
    assert(RecordType::IsValidState(newstate));
    //...
    if (! access_was_successful) return RecordType ::xInvalid;
    return newstate;
}

Satu-satunya cara untuk memastikan keamanan nilai yang benar adalah dengan menggunakan kelas khusus dengan kelebihan operator dan dibiarkan sebagai latihan untuk pembaca lain.


1
Sebagian besar jawaban yang bagus - tetapi pertanyaan menetapkan bahwa bendera dapat digabungkan dan fungsi IsValidState () tidak memungkinkan mereka untuk digabungkan.
Jonathan Leffler

3
@ Jonathan Leffler: dari tempat saya berdiri saya pikir 'IsValidState' tidak seharusnya melakukan itu, 'IsValidMask' adalah.
João Portela

1
Apakah diinginkan yang IsValidMasktidak mengizinkan memilih tidak ada (yaitu 0)?
Joachim Sauer

2
−1 Ide pengecekan tipe runtime adalah kekejian.
Ceria dan hth. - Alf

54

Lupakan definisi

Mereka akan mencemari kode Anda.

bitfields?

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

Jangan pernah gunakan itu . Anda lebih mementingkan kecepatan daripada menghemat 4 int. Menggunakan bidang bit sebenarnya lebih lambat dari akses ke jenis lainnya.

Namun, anggota bit dalam struct memiliki kelemahan praktis. Pertama, urutan bit dalam memori bervariasi dari kompiler ke kompiler. Selain itu, banyak kompiler populer menghasilkan kode yang tidak efisien untuk membaca dan menulis anggota bit , dan ada potensi masalah keamanan yang parah terkait dengan bidang bit (terutama pada sistem multiprosesor) karena fakta bahwa sebagian besar mesin tidak dapat memanipulasi set bit sewenang-wenang dalam memori, tetapi sebaliknya harus memuat dan menyimpan seluruh kata. misalnya yang berikut ini tidak akan aman untuk thread, terlepas dari penggunaan mutex

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?

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

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:

namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

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.

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

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:

enum RecordType { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;

void doSomething(RecordType p_eMyEnum)
{
   if(p_eMyEnum == xNew)
   {
       // etc.
   }
}

Seperti yang Anda lihat, enum Anda mencemari namespace global. Jika Anda meletakkan enum ini di namespace, Anda akan memiliki sesuatu seperti:

namespace RecordType {
   enum Value { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;
}

void doSomething(RecordType::Value p_eMyEnum)
{
   if(p_eMyEnum == RecordType::xNew)
   {
       // etc.
   }
}

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:

// Header.hpp
namespace RecordType {
    extern const uint8 xNew ;
    extern const uint8 xDeleted ;
    extern const uint8 xModified ;
    extern const uint8 xExisting ;
}

Dan:

// Source.hpp
namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

Anda tidak akan dapat menggunakan sakelar pada konstanta itu. Jadi pada akhirnya, pilih racunmu ... :-p


5
Menurut Anda mengapa bitfield lambat? Apakah Anda benar-benar membuat profil menggunakan kode dan metode lain? Bahkan jika ya, kejelasan bisa lebih penting daripada kecepatan, membuat "jangan pernah gunakan itu" sedikit disederhanakan.
Berkumandang

"static const uint8 xNew;" hanya redundan karena dalam variabel C ++ const namespace-scoped default ke internal linkage. Hapus "const" dan memiliki tautan eksternal. Juga, "enum {...} RecordType;" mendeklarasikan variabel global bernama "RecordType" yang jenisnya adalah anum enum.
bk1e

onebyone: Pertama, alasan utama adalah bahwa keuntungan (beberapa byte, jika ada) dibayangi oleh kehilangan (lebih lambat untuk mengakses, baik membaca dan menulis) ...
paercebal

3
onebyone: Kedua, Semua kode yang saya hasilkan di tempat kerja atau di rumah secara inheren aman. Sangat mudah dilakukan: Tidak ada global, tidak ada statis, tidak dibagi di antara utas kecuali dilindungi kunci. Menggunakan idiom ini akan mematahkan keamanan utas dasar ini. Dan untuk apa? Mungkin beberapa byte ? ... :-) ...
paercebal

Menambahkan referensi ke artikel Raymond Chen tentang biaya tersembunyi bitfield.
paercebal

30

Sudahkah Anda mengesampingkan std :: bitset? Set bendera adalah untuk apa itu. Melakukan

typedef std::bitset<4> RecordType;

kemudian

static const RecordType xNew(1);
static const RecordType xDeleted(2);
static const RecordType xModified(4);
static const RecordType xExisting(8);

Karena ada banyak operator kelebihan bit, Anda sekarang dapat melakukannya

RecordType rt = whatever;      // unsigned long or RecordType expression
rt |= xNew;                    // set 
rt &= ~xDeleted;               // clear 
if ((rt & xModified) != 0) ... // test

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.


1
Sebenarnya, saya sama sekali tidak mempertimbangkan bitset. Namun, saya tidak yakin itu akan baik. Dengan bitset, saya harus mengalamatkan bit sebagai 1, 2, 3, 4 yang akan membuat kode kurang mudah dibaca - artinya saya mungkin akan menggunakan enum untuk 'memberi nama' pada bit. Bisa jadi penghemat ruang. Terima kasih.
Milan Babuškov

Milan, Anda tidak perlu "memberi nama" bit menggunakan enum, Anda bisa menggunakan bit yang telah ditentukan seperti yang ditunjukkan di atas. Jika Anda ingin mengaktifkan bit satu, daripada my_bitset.flip (1), Anda akan melakukan my_bitset | = xNew;
moswald

ini diarahkan kurang pada Anda dan lebih pada STL, tetapi: saya benar-benar harus bertanya: mengapa Anda menggunakan bitsetini? biasanya diterjemahkan menjadi long(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, constexprdengan penyimpanan nol)
underscore_d

[edit batas waktu] ... tetapi kemudian saya tidak pernah benar-benar memahami alasan untuk bitsetkelas, selain dari apa yang tampaknya menjadi arus bawah berulang dalam diskusi sekitar 'ugh, kita harus menutupi akar tingkat rendah yang tidak sopan dari bahasa '
underscore_d

" uint8variabel dan parameter mungkin tidak akan menggunakan tumpukan kurang dari ints" salah. Jika Anda memiliki CPU dengan register 8-Bit, intperlu setidaknya 2 register sementara uint8_thanya 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, seharusnya uint8_ttidak uint8)
12431234123412341234123


5

Jika mungkin, JANGAN gunakan makro. Mereka tidak terlalu dikagumi ketika datang ke C ++ modern.


4
Benar. Yang saya benci tentang makro sendiri adalah bahwa Anda tidak dapat melangkah ke dalamnya jika mereka salah.
Carl

Saya membayangkan itu adalah sesuatu yang bisa diperbaiki di kompiler.
celticminstrel

4

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.


4

Dengan mendefinisikan saya kehilangan keamanan tipe

Belum tentu...

// signed defines
#define X_NEW      0x01u
#define X_NEW      (unsigned(0x01))  // if you find this more readable...

dan dengan enum saya kehilangan beberapa ruang (bilangan bulat)

Tidak harus - tetapi Anda harus eksplisit di titik penyimpanan ...

struct X
{
    RecordType recordType : 4;  // use exactly 4 bits...
    RecordType recordType2 : 4;  // use another 4 bits, typically in the same byte
    // of course, the overall record size may still be padded...
};

dan mungkin harus dilemparkan ketika saya ingin melakukan operasi bitwise.

Anda dapat membuat operator untuk menghilangkan rasa sakit itu:

RecordType operator|(RecordType lhs, RecordType rhs)
{
    return RecordType((unsigned)lhs | (unsigned)rhs);
}

Dengan const saya pikir saya juga kehilangan keamanan jenis karena uint8 acak bisa masuk karena kesalahan.

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.

Apakah ada cara lain yang lebih bersih? / Jika tidak, apa yang akan Anda gunakan dan mengapa?

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":

enum RecordType { New, Deleted, Modified, Existing };

showRecords([](RecordType r) { return r == New || r == Deleted; });

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.


1
+1 Baik. Tetapi operator|harus dilemparkan ke tipe integer ( unsigned int) sebelum instruksi |. Jika tidak maka operator|akan memanggil dirinya sendiri secara berulang dan menyebabkan stack run-time overflow. Saya sarankan: return RecordType( unsigned(lhs) | unsigned(rhs) );. Cheers
olibre

3

Bahkan 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.


Anda dapat menentukan tipe yang mendasarinya dalam C ++, pada revisi bahasa C ++ 11. Sampai saat itu, saya percaya itu "setidaknya cukup besar untuk disimpan dan digunakan sebagai bidang bit untuk semua enumerator yang ditentukan, tetapi mungkin intkecuali itu terlalu kecil". [Jika Anda tidak menentukan tipe yang mendasarinya dalam C ++ 11, itu menggunakan perilaku lama. Sebaliknya, enum classtipe dasar C ++ 11 secara eksplisit default ke intjika tidak ditentukan.]
Justin Time - Reinstate Monica

3

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!


Sepertinya butuh sedikit demi sedikit untuk aplikasi kecil saya. tapi sepertinya itu solusi yang bagus.
Milan Babuškov

2

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:

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

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.


Terkadang secara keseluruhan, terkadang sebagai bendera. Dan, saya juga perlu menguji apakah bendera tertentu diatur (ketika saya lulus secara keseluruhan).
Milan Babuškov

baik, ketika terpisah, minta saja int. Saat bersama, melewati struct.
Berkumandang

Itu tidak akan lebih baik. Akses ke bidang bit lebih lambat dari apa pun.
paercebal

Betulkah? Anda pikir kompiler akan menghasilkan kode yang sangat berbeda untuk menguji bit-field daripada twiddling bit manual? Dan itu akan jauh lebih lambat? Mengapa? Satu hal yang tidak dapat Anda lakukan dengan mudah secara idiomatis adalah menutupi beberapa flag sekaligus.
Berkumandang

Menjalankan tes membaca sederhana, saya mendapatkan 5,50-5,58 detik untuk bit-masking vs 5,45-5,59 untuk akses bit-field. Cukup banyak yang tidak bisa dibedakan.
Berkumandang

2

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:

#define X_NEW      (1 << 0)
#define X_DELETED  (1 << 1)
#define X_MODIFIED (1 << 2)
#define X_EXISTING (1 << 3)

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.


1
Ada cukup contoh untuk itu, khususnya dalam konstanta untuk ioctl (). Saya lebih suka menggunakan konstanta hex, 0x01, 0x02, 0x04, 0x08, 0x10, ...
Jonathan Leffler

2

Berdasarkan KISS , kohesi tinggi dan kopling rendah , ajukan pertanyaan ini -

  • Siapa yang perlu tahu? kelas saya, perpustakaan saya, kelas lain, perpustakaan lain, pihak ke-3
  • Level abstraksi apa yang harus saya sediakan? Apakah konsumen mengerti operasi bit.
  • Apakah saya harus antarmuka dari VB / C # dll?

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.


1
a) 5-6 kelas. b) hanya saya, ini proyek satu orang c) tidak ada antarmuka
Milan Babuškov

2

Jika Anda menggunakan Qt, Anda harus mencari QFlags . Kelas QFlags menyediakan cara yang aman untuk menyimpan kombinasi AT atau nilai enum.


Tidak, tidak Qt. Sebenarnya, ini adalah proyek wxWidgets.
Milan Babuškov

0

Saya lebih suka pergi dengan

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

Hanya karena:

  1. Ini lebih bersih dan membuat kode dapat dibaca dan dipelihara.
  2. Secara logis mengelompokkan konstanta.
  3. Waktu programmer lebih penting, kecuali tugas Anda adalah untuk menyimpan 3 byte tersebut.

Yah, saya bisa dengan mudah memiliki sejuta contoh dari Catatan kelas, jadi mungkin ini penting. OTOH, itu hanya perbedaan antara 1MB dan 4MB, jadi mungkin saya tidak perlu khawatir.
Milan Babuškov

@Vivek: Sudahkah Anda mempertimbangkan batasan lebar integer? Khususnya sebelum C ++ 11.
user2672165

0

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 ...


Sayangnya, deklarasi harus dalam file .h karena digunakan di seluruh proyek (digunakan oleh beberapa kelas 5-6).
Milan Babuškov
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.