ruang nama untuk jenis enum - praktik terbaik

102

Seringkali, seseorang membutuhkan beberapa jenis yang disebutkan bersama-sama. Terkadang, seseorang memiliki benturan nama. Dua solusi untuk hal ini muncul dalam pikiran: gunakan namespace, atau gunakan nama elemen enum 'lebih besar'. Namun, solusi namespace memiliki dua kemungkinan implementasi: kelas dummy dengan enum bersarang, atau namespace lengkap.

Saya mencari pro dan kontra dari ketiga pendekatan tersebut.

Contoh:

// oft seen hand-crafted name clash solution
enum eColors { cRed, cColorBlue, cGreen, cYellow, cColorsEnd };
enum eFeelings { cAngry, cFeelingBlue, cHappy, cFeelingsEnd };
void setPenColor( const eColors c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cColorBlue: //...
        //...
    }
 }


// (ab)using a class as a namespace
class Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
class Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
 }


 // a real namespace?
 namespace Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
 namespace Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
 void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
  }
xtofl
sumber
19
Pertama-tama, saya akan menggunakan Color :: Red, Feeling: Angry, dll
abatishchev
pertanyaan yang bagus, saya menggunakan metode namespace ....;)
MiniScalope
18
awalan "c" pada segala hal merusak keterbacaan.
Pengguna
8
@ Pengguna: mengapa, terima kasih telah membuka diskusi Hongaria :)
xtofl
5
Perhatikan bahwa Anda tidak perlu memberi nama enum seperti in enum e {...}, enum bisa anonim, yaitu enum {...}, yang jauh lebih masuk akal saat dibungkus dalam namespace atau kelas.
kralyk

Jawaban:

73

Jawaban C ++ 03 asli:

The manfaat dari namespace(atas class) adalah bahwa Anda dapat menggunakan usingdeklarasi ketika Anda ingin.

The masalah dengan menggunakan namespaceadalah bahwa ruang nama dapat diperluas di tempat lain dalam kode. Dalam proyek besar, Anda tidak akan dijamin bahwa dua enum berbeda tidak menganggap keduanya dipanggileFeelings

Untuk kode yang tampak lebih sederhana, saya menggunakan a struct, karena Anda mungkin ingin isinya menjadi publik.

Jika Anda melakukan salah satu dari praktik ini, Anda berada di depan kurva dan mungkin tidak perlu meneliti ini lebih jauh.

Lebih baru, saran C ++ 11:

Jika Anda menggunakan C ++ 11 atau yang lebih baru, enum classsecara implisit akan mencakup nilai enum dalam nama enum.

Dengan enum classAnda akan kehilangan konversi implisit dan perbandingan dengan tipe integer, tetapi dalam praktiknya itu dapat membantu Anda menemukan kode yang ambigu atau buggy.

Drew Dormann
sumber
4
Saya setuju dengan gagasan-struct. Dan terima kasih atas pujiannya :)
xtofl
3
+1 Saya tidak dapat mengingat sintaks "enum class" C ++ 11. Tanpa fitur itu, enum tidak lengkap.
Grault
Apakah mereka pilihan untuk menggunakan "menggunakan" untuk cakupan implisit dari "kelas enum". mis. Akan menambahkan 'using Color :: e;' untuk membuat kode mengizinkan penggunaan 'cRed' dan mengetahui ini seharusnya Color :: e :: cRed?
JVA buka
20

FYI Di C ++ 0x ada sintaks baru untuk kasus seperti yang Anda sebutkan (lihat halaman wiki C ++ 0x )

enum class eColors { ... };
enum class eFeelings { ... };
jmihalicza
sumber
11

Saya telah menggabungkan jawaban sebelumnya menjadi seperti ini: (EDIT: Ini hanya berguna untuk pra-C ++ 11. Jika Anda menggunakan C ++ 11, gunakan enum class)

Saya punya satu file header besar yang berisi semua enum proyek saya, karena enum ini dibagi antara kelas pekerja dan tidak masuk akal untuk meletakkan enum di kelas pekerja itu sendiri.

The structmenghindari publik: gula sintaksis, dan typedefmemungkinkan Anda benar-benar mendeklarasikan variabel enum ini dalam kelas pekerja lain.

Saya tidak berpikir menggunakan namespace membantu sama sekali. Mungkin ini karena saya programmer C #, dan di sana Anda harus menggunakan nama tipe enum saat merujuk nilai, jadi saya sudah terbiasa.

    struct KeySource {
        typedef enum { 
            None, 
            Efuse, 
            Bbram
        } Type;
    };

    struct Checksum {
        typedef enum {
            None =0,
            MD5 = 1,
            SHA1 = 2,
            SHA2 = 3
        } Type;
    };

    struct Encryption {
        typedef enum {
            Undetermined,
            None,
            AES
        } Type;
    };

    struct File {
        typedef enum {
            Unknown = 0,
            MCS,
            MEM,
            BIN,
            HEX
        } Type;
    };

...

class Worker {
    File::Type fileType;
    void DoIt() {
       switch(fileType) {
       case File::MCS: ... ;
       case File::MEM: ... ;
       case File::HEX: ... ;
    }
}
Mark Lakata
sumber
9

Saya pasti akan menghindari menggunakan kelas untuk ini; gunakan namespace sebagai gantinya. Pertanyaan intinya adalah apakah akan menggunakan namespace atau menggunakan id unik untuk nilai enum. Secara pribadi, saya akan menggunakan namespace sehingga id saya bisa lebih pendek dan mudah-mudahan lebih jelas. Kemudian kode aplikasi dapat menggunakan arahan 'using namespace' dan membuat semuanya lebih mudah dibaca.

Dari contoh Anda di atas:

using namespace Colors;

void setPenColor( const e c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cBlue: //...
        //...
    }
}
Charles Anderson
sumber
Bisakah Anda memberi petunjuk tentang mengapa Anda lebih memilih namespace daripada kelas?
xtofl
@xtofl: Anda tidak dapat menulis 'menggunakan Class Colors`
MSalters
2
@ MSalters: Anda juga tidak dapat menulis Colors someColor = Red;, karena namespace bukan merupakan suatu tipe. Anda harus menulis Colors::e someColor = Red;sebagai gantinya, yang cukup kontra-intuitif.
SasQ
@SasQ Tidakkah Anda harus menggunakan Colors::e someColorbahkan dengan a struct/classjika Anda ingin menggunakannya dalam switchpernyataan? Jika Anda menggunakan anonim enummaka sakelar tidak akan dapat mengevaluasi file struct.
Macbeth's Enigma
1
Maaf, tapi const e csepertinya sulit dibaca oleh saya :-) Jangan lakukan itu. Namun menggunakan namespace, boleh saja.
dhaumann
7

Perbedaan antara menggunakan kelas atau namespace adalah bahwa kelas tidak dapat dibuka kembali seperti yang bisa dilakukan namespace. Hal ini menghindari kemungkinan bahwa namespace mungkin disalahgunakan di masa mendatang, tetapi ada juga masalah yang tidak dapat Anda tambahkan ke kumpulan enumerasi juga.

Manfaat yang mungkin untuk menggunakan kelas, adalah bahwa mereka dapat digunakan sebagai argumen jenis template, yang tidak berlaku untuk namespace:

class Colors {
public:
  enum TYPE {
    Red,
    Green,
    Blue
  };
};

template <typename T> void foo (T t) {
  typedef typename T::TYPE EnumType;
  // ...
}

Secara pribadi, saya bukan penggemar penggunaan , dan saya lebih suka nama yang sepenuhnya memenuhi syarat, jadi saya tidak benar-benar melihatnya sebagai nilai tambah untuk namespace. Namun, ini mungkin bukan keputusan terpenting yang akan Anda buat dalam proyek Anda!

Richard Corden
sumber
Tidak membuka kembali kelas juga berpotensi merugikan. Daftar warnanya juga tidak terbatas.
MSalters
1
Saya pikir tidak mengulang kelas adalah keuntungan potensial. Jika saya ingin lebih banyak warna, saya hanya akan mengkompilasi ulang kelas dengan lebih banyak warna. Jika saya tidak dapat melakukan ini (katakanlah saya tidak memiliki kodenya), maka saya tidak ingin menyentuhnya dalam hal apa pun.
Thomas Eding
@ MSalters: Tidak ada kemungkinan untuk membuka kembali kelas bukan hanya merugikan, tetapi juga alat keamanan. Karena ketika dimungkinkan untuk membuka kembali kelas dan menambahkan beberapa nilai ke enum, itu bisa merusak kode perpustakaan lain yang sudah bergantung pada enum itu dan hanya mengetahui kumpulan nilai lama. Itu kemudian akan dengan senang hati menerima nilai-nilai baru itu, tetapi berhenti pada waktu proses karena tidak tahu apa yang harus dilakukan dengannya. Ingat Prinsip Terbuka-Tertutup: Kelas harus ditutup untuk modifikasi , tetapi terbuka untuk diperpanjang . Dengan memperluas maksud saya tidak menambahkan ke kode yang sudah ada, tetapi membungkusnya dengan kode baru (misalnya, menurunkan).
SasQ
Jadi ketika Anda ingin memperluas enum, Anda harus membuatnya menjadi tipe baru yang diturunkan dari yang pertama (jika saja itu dimungkinkan dengan mudah di C ++ ...; /). Kemudian dapat digunakan dengan aman oleh kode baru yang memahami nilai-nilai baru tersebut, tetapi hanya nilai lama yang akan diterima (dengan mengonversinya) oleh kode lama. Mereka seharusnya tidak menerima satu pun dari nilai-nilai baru itu sebagai jenis yang salah (yang diperpanjang). Hanya nilai lama yang mereka pahami yang diterima sebagai jenis (dasar) yang benar (dan secara tidak sengaja juga merupakan jenis baru, sehingga dapat diterima oleh kode baru juga).
SasQ
7

Keuntungan menggunakan kelas adalah Anda dapat membangun kelas yang lengkap di atasnya.

#include <cassert>

class Color
{
public:
    typedef enum
    {
        Red,
        Blue,
        Green,
        Yellow
    } enum_type;

private:
    enum_type _val;

public:
    Color(enum_type val = Blue)
        : _val(val)
    {
        assert(val <= Yellow);
    }

    operator enum_type() const
    {
        return _val;
    }
};

void SetPenColor(const Color c)
{
    switch (c)
    {
        case Color::Red:
            // ...
            break;
    }
}

Seperti yang ditunjukkan contoh di atas, dengan menggunakan kelas Anda dapat:

  1. melarang (sayangnya, bukan waktu kompilasi) C ++ mengizinkan cast dari nilai yang tidak valid,
  2. setel default (bukan nol) untuk enum yang baru dibuat,
  3. tambahkan metode lebih lanjut, seperti mengembalikan representasi string pilihan.

Perhatikan saja bahwa Anda perlu mendeklarasikan operator enum_type()agar C ++ tahu cara mengubah kelas Anda menjadi enum yang mendasarinya. Jika tidak, Anda tidak akan bisa meneruskan tipe ke switchpernyataan.

Michał Górny
sumber
Apakah solusi ini terkait dengan apa yang ditampilkan di sini ?: en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Type_Safe_Enum Saya sedang berpikir tentang cara membuatnya menjadi template yang tidak perlu saya tulis ulang setiap saat Saya perlu menggunakannya.
SasQ
@SasQ: sepertinya mirip ya. Itu mungkin ide yang sama. Namun, saya tidak yakin apakah templat itu bermanfaat kecuali Anda menambahkan banyak metode 'umum' di sana.
Michał Górny
1. Tidak sepenuhnya benar. Anda dapat meminta waktu kompilasi untuk memeriksa apakah enumnya valid, melalui const_expr, atau melalui konstruktor pribadi untuk int.
xryl669
5

Karena enum tercakup dalam cakupannya, mungkin yang terbaik adalah membungkusnya dengan sesuatu untuk menghindari mencemari namespace global dan membantu menghindari benturan nama. Saya lebih suka namespace daripada kelas hanya karena namespaceterasa seperti sekantong penyimpanan, sedangkan classterasa seperti objek yang kuat (lih. Debat structvs. class). Manfaat yang mungkin untuk namespace adalah bahwa ia dapat diperpanjang nanti - berguna jika Anda berurusan dengan kode pihak ketiga yang tidak dapat Anda modifikasi.

Ini semua tentu saja bisa diperdebatkan ketika kita mendapatkan kelas enum dengan C ++ 0x.

Michael Kristofik
sumber
kelas enum ... perlu melihat itu!
xtofl
3

Saya juga cenderung menyelesaikan enum saya di kelas.

Seperti yang diisyaratkan oleh Richard Corden, keuntungan dari sebuah kelas adalah ia merupakan tipe dalam pengertian c ++ sehingga Anda dapat menggunakannya dengan template.

Saya memiliki toolbox :: kelas Enum khusus untuk kebutuhan saya yang saya spesialisasikan untuk setiap template yang menyediakan fungsi dasar (terutama: memetakan nilai enum ke std :: string sehingga I / O lebih mudah dibaca).

Template kecil saya juga memiliki manfaat tambahan untuk benar-benar memeriksa nilai yang diizinkan. Kompiler agak lalai dalam memeriksa apakah nilainya benar-benar ada di enum:

typedef enum { False: 0, True: 2 } boolean;
   // The classic enum you don't want to see around your code ;)

int main(int argc, char* argv[])
{
  boolean x = static_cast<boolean>(1);
  return (x == False || x == True) ? 0 : 1;
} // main

Itu selalu mengganggu saya bahwa kompiler tidak akan menangkap ini, karena Anda ditinggalkan dengan nilai enum yang tidak masuk akal (dan Anda tidak akan mengharapkannya).

Demikian pula:

typedef enum { Zero: 0, One: 1, Two: 2 } example;

int main(int argc, char* argv[])
{
  example y = static_cast<example>(3);
  return (y == Zero || y == One || y == Two) ? 0 : 1;
} // main

Sekali lagi main akan mengembalikan kesalahan.

Masalahnya adalah bahwa kompilator akan menyesuaikan enum dalam representasi terkecil yang tersedia (di sini kita membutuhkan 2 bit) dan semua yang cocok dalam representasi ini dianggap sebagai nilai yang valid.

Ada juga masalah bahwa terkadang Anda lebih suka memiliki loop pada nilai yang mungkin daripada sebuah sakelar sehingga Anda tidak perlu mengubah semua sakelar Anda setiap kali Anda menambahkan nilai ke enum.

Semua dalam semua penolong kecil saya benar-benar memudahkan hal-hal untuk enum saya (tentu saja, ini menambahkan beberapa overhead) dan itu hanya mungkin karena saya menyusun setiap enum di struct sendiri :)

Matthieu M.
sumber
4
Menarik. Apakah Anda keberatan berbagi definisi kelas Enum Anda?
momeara