Teruskan mendeklarasikan enum dalam C ++


265

Saya mencoba melakukan sesuatu seperti berikut:

enum E;

void Foo(E e);

enum E {A, B, C};

yang ditolak kompilator. Saya sudah melihat sekilas di Google dan konsensusnya adalah "Anda tidak bisa melakukannya", tetapi saya tidak mengerti mengapa. Adakah yang bisa menjelaskan?

Klarifikasi 2: Saya melakukan ini karena saya memiliki metode pribadi di kelas yang mengambil kata enum, dan saya tidak ingin nilai-nilai enum terbuka - jadi, misalnya, saya tidak ingin ada yang tahu bahwa E didefinisikan sebagai

enum E {
    FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X
}

sebagai proyek X bukanlah sesuatu yang saya ingin pengguna saya ketahui.

Jadi, saya ingin meneruskan mendeklarasikan enum sehingga saya bisa meletakkan metode pribadi di file header, mendeklarasikan enum secara internal di cpp, dan mendistribusikan file library yang dibangun dan header ke orang.

Adapun kompiler - itu GCC.


Bertahun-tahun dalam hal ini dan entah bagaimana StackOverflow menarik saya kembali;) Sebagai saran postmortem - jangan lakukan ini terutama dalam skenario yang Anda gambarkan. Saya lebih suka untuk mendefinisikan antarmuka abstrak dan mengekspos pengguna ini dan menjaga definisi enum dan semua detail implementasi lainnya dengan implementasi internal yang tidak ada orang lain melihat di sisi saya memungkinkan saya untuk melakukan apa saja kapan saja dan memiliki kontrol penuh kapan pengguna melihat apa pun.
RnR

Jika Anda membaca melewati jawaban yang diterima, ini sepenuhnya mungkin karena C ++ 11.
fuzzyTew

Jawaban:


217

Alasan enum tidak dapat diteruskan dinyatakan adalah bahwa tanpa mengetahui nilai-nilai, kompiler tidak dapat mengetahui penyimpanan yang diperlukan untuk variabel enum. C ++ Compiler diperbolehkan untuk menentukan ruang penyimpanan aktual berdasarkan ukuran yang diperlukan untuk memuat semua nilai yang ditentukan. Jika semua yang terlihat adalah deklarasi maju, unit terjemahan tidak dapat mengetahui ukuran penyimpanan apa yang akan dipilih - bisa berupa char atau int, atau yang lainnya.


Dari Bagian 7.2.5 Standar ISO C ++:

The jenis yang mendasari sebuah enumerasi adalah tipe integral yang dapat mewakili semua nilai pencacah didefinisikan dalam pencacahan. Ini adalah implementasi-didefinisikan jenis integral yang digunakan sebagai tipe yang mendasari untuk enumerasi kecuali bahwa tipe yang mendasari tidak boleh lebih besar dari intkecuali jika nilai enumerator tidak dapat ditampung dalam suatu intatau unsigned int. Jika daftar enumerator kosong, tipe yang mendasari adalah seolah-olah enumerasi memiliki enumerator tunggal dengan nilai 0. Nilai yang sizeof()diterapkan pada tipe enumerasi, objek tipe enumerasi, atau enumerator, adalah nilai yang sizeof()diterapkan pada tipe yang mendasarinya.

Karena penelepon ke fungsi harus mengetahui ukuran parameter untuk mengatur tumpukan panggilan dengan benar, jumlah enumerasi dalam daftar enumerasi harus diketahui sebelum prototipe fungsi.

Pembaruan: Dalam C ++ 0X sintaks untuk kata kunci yang menyatakan jenis enum telah diusulkan dan diterima. Anda dapat melihat proposal di http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf


29
-1. Alasan Anda tidak bisa benar - jika tidak, mengapa Anda diizinkan meneruskan pernyataan "kelas C;" dan kemudian mendeklarasikan prototipe fungsi yang mengambil atau mengembalikan C, sebelum sepenuhnya mendefinisikan C?
j_random_hacker

112
@ j_random: Anda tidak dapat menggunakan kelas sebelum didefinisikan sepenuhnya - Anda hanya dapat menggunakan pointer atau referensi ke kelas itu dan itu karena ukuran dan cara operasinya tidak bergantung pada apa kelasnya.
RnR

27
Ukuran referensi atau pointer ke objek kelas diatur oleh kompiler, dan tidak tergantung pada ukuran sebenarnya objek - itu adalah ukuran pointer dan referensi. Enum adalah objek, dan ukurannya diperlukan agar kompiler dapat mengakses penyimpanan yang benar.
KJAWolf

17
Logikanya akan dapat mendeklarasikan pointer / referensi ke enum jika kita memiliki maju-mendeklarasikan enum, seperti yang dapat kita lakukan dengan kelas. Hanya saja Anda tidak sering berurusan dengan pointer ke enum :)
Pavel Minaev

20
Saya tahu diskusi ini berakhir sejak lama, tetapi saya harus berbaris dengan @j_random_hacker di sini: masalahnya di sini bukan tentang pointer atau referensi ke tipe tidak lengkap, tetapi tentang penggunaan tipe tidak lengkap dalam deklarasi. Karena itu legal untuk dilakukan struct S; void foo(S s);(catatan yang foohanya dideklarasikan, tidak didefinisikan), maka tidak ada alasan mengapa kita tidak dapat melakukannya enum E; void foo(E e);juga. Dalam kedua kasus, ukurannya tidak diperlukan.
Luc Touraille

201

Deklarasi maju enum dimungkinkan karena C ++ 11. Sebelumnya, alasan tipe enum tidak dapat diteruskan adalah karena ukuran enumerasi tergantung pada isinya. Selama ukuran enumerasi ditentukan oleh aplikasi, itu dapat dinyatakan maju:

enum Enum1;                   //Illegal in C++ and C++0x; no size is explicitly specified.
enum Enum2 : unsigned int;    //Legal in C++0x.
enum class Enum3;             //Legal in C++0x, because enum class declarations have a default type of "int".
enum class Enum4: unsigned int; //Legal C++0x.
enum Enum2 : unsigned short;  //Illegal in C++0x, because Enum2 was previously declared with a different type.

1
Apakah ada dukungan kompiler untuk fitur ini? GCC 4,5 tampaknya tidak memilikinya :(
rubenvb

4
@rubenvb Begitu juga Visual C ++ 11 (2012) blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx
knatten

Saya mencari enum32_t dan dengan jawaban Anda enum XXX: uint32_t {a, b, c};
fantastory

Saya pikir enum scoped (kelas enum) diimplementasikan dalam C ++ 11? Jika demikian, bagaimana cara mereka legal di C ++ 0X?
Terrabits

1
C ++ 0x adalah nama kerja untuk C ++ 11, @Terrabits, sebelum secara resmi distandarisasi. Logikanya adalah bahwa jika suatu fitur diketahui (atau sangat mungkin) untuk dimasukkan dalam standar yang diperbarui, maka penggunaan fitur itu sebelum standar dirilis secara resmi cenderung menggunakan nama yang berfungsi. (Misalnya, kompiler yang mendukung fitur C ++ 11 sebelum standardisasi resmi pada tahun 2011 memiliki dukungan C ++ 0x, kompiler yang mendukung fitur C ++ 17 sebelum standardisasi resmi memiliki dukungan C ++ 1z, dan kompiler yang mendukung fitur C ++ 20 sekarang (2019) memiliki dukungan C ++ 2a.)
Justin Time - Reinstate Monica

79

Saya menambahkan jawaban terkini di sini, mengingat perkembangan terakhir.

Anda dapat meneruskan-mendeklarasikan enum di C ++ 11, selama Anda mendeklarasikan tipe penyimpanannya pada saat yang sama. Sintaksnya terlihat seperti ini:

enum E : short;
void foo(E e);

....

enum E : short
{
    VALUE_1,
    VALUE_2,
    ....
}

Bahkan, jika fungsi tidak pernah merujuk pada nilai-nilai enumerasi, Anda tidak perlu deklarasi lengkap sama sekali pada saat itu.

Ini didukung oleh G ++ 4.6 dan seterusnya ( -std=c++0xatau -std=c++11dalam versi yang lebih baru). Visual C ++ 2013 mendukung ini; dalam versi sebelumnya ia memiliki semacam dukungan non-standar yang belum saya temukan - saya menemukan beberapa saran bahwa deklarasi maju sederhana itu legal, tetapi YMMV.


4
Memberi +1 karena ini adalah satu-satunya jawaban yang menyebutkan Anda perlu mendeklarasikan tipe dalam deklarasi Anda serta definisi Anda.
turoni

Saya percaya dukungan parsial dalam MSVC awal didukung dari C ++ / CLI enum classsebagai ekstensi C ++ (sebelum C ++ 11 berbeda enum class), setidaknya jika saya ingat dengan benar. Kompiler memungkinkan Anda untuk menentukan jenis enum yang mendasarinya, tetapi tidak mendukung enum classatau meneruskan enum, dan memperingatkan Anda bahwa kualifikasi enumerator dengan cakupan enum adalah ekstensi yang tidak standar. Saya ingat itu bekerja kira-kira sama dengan menentukan tipe yang mendasari di C ++ 11 tidak, kecuali lebih menjengkelkan karena Anda harus menekan peringatan.
Justin Time - Pasang kembali Monica

30

Maju menyatakan hal-hal dalam C ++ sangat berguna karena secara dramatis mempercepat waktu kompilasi . Anda maju dapat mendeklarasikan beberapa hal di C ++ termasuk: struct, class, function, dll ...

Tapi bisakah Anda meneruskan menyatakan enumdalam C ++?

Tidak, kamu tidak bisa.

Tapi mengapa tidak membiarkannya? Jika diizinkan, Anda dapat menentukan enumjenis Anda di file header Anda, dan enumnilai - nilai Anda di file sumber Anda. Kedengarannya seperti itu harus diizinkan kan?

Salah.

Di C ++ tidak ada tipe default untuk enumseperti ada di C # (int). Dalam C ++ enumtipe Anda akan ditentukan oleh kompiler untuk menjadi tipe apa pun yang akan cocok dengan rentang nilai yang Anda miliki untuk Anda enum.

Apa artinya?

Ini berarti bahwa enumtipe dasar Anda tidak dapat sepenuhnya ditentukan sampai Anda memiliki semua nilai yang enumditentukan. Orang mana yang tidak dapat Anda pisahkan dari deklarasi dan definisi Anda enum. Dan karena itu Anda tidak dapat meneruskan mendeklarasikan enumdalam C ++.

Standar ISO C ++ S7.2.5:

Tipe yang mendasari enumerasi adalah tipe integral yang dapat mewakili semua nilai enumerator yang didefinisikan dalam enumerasi. Ini adalah implementasi-didefinisikan jenis integral yang digunakan sebagai tipe yang mendasari untuk enumerasi kecuali bahwa tipe yang mendasari tidak boleh lebih besar dari intkecuali jika nilai enumerator tidak dapat ditampung dalam suatu intatau unsigned int. Jika daftar enumerator kosong, tipe yang mendasari adalah seolah-olah enumerasi memiliki enumerator tunggal dengan nilai 0. Nilai yang sizeof()diterapkan pada tipe enumerasi, objek tipe enumerasi, atau enumerator, adalah nilai yang sizeof()diterapkan pada tipe yang mendasarinya.

Anda dapat menentukan ukuran tipe yang disebutkan dalam C ++ dengan menggunakan sizeofoperator. Ukuran dari tipe yang disebutkan adalah ukuran dari tipe yang mendasarinya. Dengan cara ini Anda bisa menebak jenis yang digunakan kompiler untuk Anda enum.

Bagaimana jika Anda menentukan jenis Anda enumsecara eksplisit seperti ini:

enum Color : char { Red=0, Green=1, Blue=2};
assert(sizeof Color == 1);

Bisakah Anda meneruskan menyatakan Anda enum?

Tapi mengapa tidak?

Menentukan jenis suatu enumsebenarnya bukan bagian dari standar C ++ saat ini. Ini adalah ekstensi VC ++. Ini akan menjadi bagian dari C ++ 0x.

Sumber


15
Jawaban ini sekarang beberapa tahun kedaluwarsa.
Tom

Waktu membodohi kita semua. Komentar Anda sekarang sudah ketinggalan zaman; jawabannya satu dekade!
pjcard

14

[Jawaban saya salah, tetapi saya meninggalkannya di sini karena komentarnya bermanfaat].

Penerusan mendeklarasikan enum adalah non-standar, karena pointer ke tipe enum yang berbeda tidak dijamin ukurannya sama. Compiler mungkin perlu melihat definisi untuk mengetahui ukuran pointer apa yang dapat digunakan dengan tipe ini.

Dalam praktiknya, setidaknya pada semua kompiler populer, pointer ke enum adalah ukuran yang konsisten. Deklarasi maju enum disediakan sebagai ekstensi bahasa oleh Visual C ++, misalnya.


2
-1. Jika alasan Anda benar, alasan yang sama akan menyiratkan bahwa deklarasi maju tipe kelas tidak dapat digunakan untuk membuat pointer ke tipe tersebut - tetapi mereka bisa.
j_random_hacker

6
+1. Penalaran itu benar. Kasus spesifik adalah platform di mana sizeof (char *)> sizeof (int *). Keduanya dapat menjadi tipe yang mendasari untuk enum, tergantung pada kisaran. Kelas tidak memiliki tipe yang mendasarinya sehingga analoginya salah.
MSalters

3
@MSalters: Contoh: "struct S {int x;};" Sekarang, sizeof (S *) harus sama dengan ukuran pointer-to-struct lainnya, karena C ++ memungkinkan pointer tersebut untuk dideklarasikan dan digunakan sebelum definisi S ...
j_random_hacker

1
@MSalters: ... Pada platform di mana sizeof (char *)> sizeof (int *), menggunakan pointer "ukuran penuh" untuk struct tertentu ini mungkin tidak efisien, tetapi secara dramatis menyederhanakan pengkodean - dan persis sama hal bisa, dan harus, dilakukan untuk tipe enum.
j_random_hacker

4
pointer ke data dan pointer ke fungsi dapat ukuran yang berbeda, tapi saya cukup yakin bahwa pointer data harus bolak-balik (dilemparkan ke tipe pointer data lain, lalu kembali ke yang asli, harus tetap bekerja), yang menyiratkan bahwa semua pointer data berukuran sama.
Ben Voigt

7

Memang tidak ada yang namanya deklarasi maju enum. Karena definisi enum tidak mengandung kode apa pun yang bisa bergantung pada kode lain menggunakan enum, biasanya bukan masalah untuk mendefinisikan enum sepenuhnya ketika Anda pertama kali mendeklarasikannya.

Jika satu-satunya penggunaan enum Anda adalah dengan fungsi anggota pribadi, Anda dapat mengimplementasikan enkapsulasi dengan menjadikan enum itu sendiri sebagai anggota pribadi kelas itu. Enum masih harus sepenuhnya didefinisikan pada titik deklarasi, yaitu, dalam definisi kelas. Namun, ini bukan masalah yang lebih besar dengan mendeklarasikan fungsi anggota privat di sana, dan bukan eksposur internal implementasi yang lebih buruk dari itu.

Jika Anda membutuhkan tingkat penyembunyian yang lebih dalam untuk detail implementasi Anda, Anda dapat memecahnya menjadi antarmuka abstrak, hanya terdiri dari fungsi virtual murni, dan implementasi antarmuka (pewarisan) antarmuka yang nyata, sangat tersembunyi. Pembuatan instance kelas dapat ditangani oleh pabrik atau fungsi anggota statis dari antarmuka. Dengan begitu, bahkan nama kelas sebenarnya, apalagi fungsi privatnya, tidak akan diekspos.


5

Hanya mencatat bahwa alasan sebenarnya adalah bahwa ukuran enum belum diketahui setelah deklarasi maju. Nah, Anda menggunakan deklarasi maju dari sebuah struct untuk dapat melewati sebuah pointer di sekitar atau merujuk ke suatu objek dari tempat yang dirujuk dalam definisi struct yang dinyatakan maju itu sendiri juga.

Meneruskan mendeklarasikan enum tidak akan terlalu berguna, karena seseorang ingin dapat membagikan enum berdasarkan nilai. Anda bahkan tidak dapat memiliki pointer ke sana, karena saya baru-baru ini diberitahu beberapa platform menggunakan pointer ukuran yang berbeda untuk char daripada untuk int atau panjang. Jadi itu semua tergantung pada isi enum.

Standar C ++ saat ini secara eksplisit melarang melakukan sesuatu seperti

enum X;

(dalam 7.1.5.3/1). Tapi berikutnya C ++ Standar karena tahun depan memungkinkan berikut, yang meyakinkan saya masalah sebenarnya memiliki hubungannya dengan jenis yang mendasari:

enum X : int;

Ini dikenal sebagai deklarasi enum "buram". Anda bahkan dapat menggunakan X dengan nilai dalam kode berikut. Dan pencacahnya nanti dapat didefinisikan dalam deklarasi ulang pencacahan nanti. Lihat 7.2di draft kerja saat ini.


4

Saya akan melakukannya dengan cara ini:

[di tajuk publik]

typedef unsigned long E;

void Foo(E e);

[di tajuk internal]

enum Econtent { FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X,
  FORCE_32BIT = 0xFFFFFFFF };

Dengan menambahkan FORCE_32BIT, kami memastikan Econtent mengkompilasi ke panjang, sehingga dapat dipertukarkan dengan E.


1
Tentu saja, ini berarti bahwa (A) jenis E dan Econtent berbeda, dan (B) pada sistem LP64, sizeof (E) = 2 * sizeof (EContent). Perbaikan sepele: ULONG_MAX, lebih mudah dibaca juga.
MSalters

2

Jika Anda benar-benar tidak ingin enum Anda muncul di file header Anda DAN memastikan bahwa itu hanya digunakan dengan metode pribadi, maka salah satu solusinya adalah dengan prinsip pimpl.

Ini adalah teknik yang memastikan untuk menyembunyikan internal kelas di header dengan hanya menyatakan:

class A 
{
public:
    ...
private:
    void* pImpl;
};

Kemudian dalam file implementasi Anda (cpp), Anda mendeklarasikan kelas yang akan menjadi representasi internal.

class AImpl
{
public:
    AImpl(A* pThis): m_pThis(pThis) {}

    ... all private methods here ...
private:
    A* m_pThis;
};

Anda harus secara dinamis membuat implementasi di konstruktor kelas dan menghapusnya di destruktor dan ketika menerapkan metode publik, Anda harus menggunakan:

((AImpl*)pImpl)->PrivateMethod();

Ada pro untuk menggunakan pimpl, salah satunya adalah decouple header kelas Anda dari implementasinya, tidak perlu mengkompilasi ulang kelas lain saat mengubah implementasi satu kelas. Lain adalah mempercepat waktu kompilasi Anda karena header Anda sangat sederhana.

Tapi itu sulit digunakan, jadi Anda harus bertanya pada diri sendiri apakah hanya menyatakan enum sebagai pribadi di header adalah masalah besar.


3
struct AImpl; struct A {private: AImpl * pImpl; };

2

Anda dapat membungkus enum dalam sebuah struct, menambahkan beberapa konstruktor dan mengetik konversi, dan meneruskan mendeklarasikan struct sebagai gantinya.

#define ENUM_CLASS(NAME, TYPE, VALUES...) \
struct NAME { \
    enum e { VALUES }; \
    explicit NAME(TYPE v) : val(v) {} \
    NAME(e v) : val(v) {} \
    operator e() const { return e(val); } \
    private:\
        TYPE val; \
}

Tampaknya ini berfungsi: http://ideone.com/TYtP2



1

Ada beberapa perbedaan pendapat karena ini terbentur (semacam), jadi inilah beberapa bit yang relevan dari standar. Penelitian menunjukkan bahwa standar tersebut tidak benar-benar mendefinisikan deklarasi maju, juga tidak secara eksplisit menyatakan bahwa enum dapat atau tidak dapat dinyatakan maju.

Pertama, dari dcl.enum, bagian 7.2:

Tipe yang mendasari enumerasi adalah tipe integral yang dapat mewakili semua nilai enumerator yang didefinisikan dalam enumerasi. Ini adalah implementasi-didefinisikan tipe integral mana yang digunakan sebagai tipe yang mendasari untuk enumerasi kecuali bahwa tipe yang mendasari tidak boleh lebih besar dari int kecuali nilai enumerator tidak dapat ditampung dalam int atau int unsigned. Jika daftar enumerator kosong, tipe yang mendasarinya adalah seolah-olah enumerasi memiliki enumerator tunggal dengan nilai 0. Nilai sizeof () diterapkan pada tipe enumerasi, objek tipe enumerasi, atau enumerator, adalah nilai dari sizeof () diterapkan pada tipe yang mendasarinya.

Jadi, jenis enum yang mendasarinya adalah implementasi yang ditentukan, dengan satu batasan kecil.

Selanjutnya kita beralih ke bagian "tipe tidak lengkap" (3,9), yaitu hampir sedekat yang kita lihat dengan standar apa pun pada deklarasi maju:

Kelas yang telah dideklarasikan tetapi tidak didefinisikan, atau array dengan ukuran yang tidak diketahui atau tipe elemen yang tidak lengkap, adalah tipe objek yang tidak didefinisikan dengan sempurna.

Tipe kelas (seperti "kelas X") mungkin tidak lengkap pada satu titik di unit terjemahan dan selesai nanti; tipe "kelas X" adalah tipe yang sama di kedua titik. Tipe objek array yang dideklarasikan mungkin berupa array tipe kelas tidak lengkap dan karenanya tidak lengkap; jika tipe kelas selesai nanti di dalam unit terjemahan, tipe array menjadi lengkap; tipe array pada kedua titik tersebut adalah tipe yang sama. Tipe objek array yang dideklarasikan mungkin berupa array dengan ukuran yang tidak diketahui dan karenanya tidak lengkap pada satu titik dalam unit terjemahan dan selesai nanti; tipe-tipe array pada kedua titik tersebut ("array dengan batas tidak diketahui T" dan "array dari N T") adalah tipe yang berbeda. Jenis pointer ke array dengan ukuran yang tidak diketahui, atau tipe yang didefinisikan oleh deklarasi typedef menjadi array dengan ukuran yang tidak diketahui,

Jadi disana, standarnya cukup banyak ditata jenis-jenis yang bisa dideklarasikan. Enum tidak ada di sana, jadi penulis kompiler umumnya menganggap forward menyatakan tidak diizinkan oleh standar karena ukuran variabel dari tipe yang mendasarinya.

Masuk akal juga. Enum biasanya dirujuk dalam situasi menurut nilai, dan kompiler memang perlu mengetahui ukuran penyimpanan dalam situasi tersebut. Karena ukuran penyimpanan adalah implementasi yang ditentukan, banyak kompiler hanya dapat memilih untuk menggunakan nilai 32 bit untuk tipe yang mendasari setiap enum, di mana pada saat itu dimungkinkan untuk meneruskannya. Eksperimen yang menarik mungkin adalah dengan mencoba mendeklarasikan enum di studio visual, kemudian memaksanya untuk menggunakan tipe dasar yang lebih besar dari sizeof (int) seperti yang dijelaskan di atas untuk melihat apa yang terjadi.


perhatikan bahwa itu secara eksplisit melarang "enum foo;" di 7.1.5.3/1 (tetapi seperti halnya semua, selama kompiler memperingatkan, itu masih dapat mengkompilasi kode tersebut, tentu saja)
Johannes Schaub - litb

Terima kasih telah menunjukkannya, itu paragraf yang sangat esoteris dan mungkin butuh waktu seminggu untuk menguraikannya. Tapi senang mengetahui itu ada di sana.
Dan Olson

jangan khawatir. Beberapa paragraf standar benar-benar aneh :) yah, specifier tipe yang diuraikan adalah sesuatu di mana Anda menentukan tipe, tetapi juga menentukan sesuatu yang lebih untuk membuatnya tidak ambigu. mis. "struct X" bukan "X", atau "enum Y" bukan hanya "Y". Anda memerlukannya untuk menyatakan sesuatu benar-benar tipe.
Johannes Schaub - litb

jadi kamu bisa menggunakannya seperti ini: "class X * foo;" jika X belum diteruskan dinyatakan belum. atau "ketikkan X :: foo" dalam templat untuk disambiguasi. atau "obj tautan kelas;" jika ada fungsi "tautan" dalam lingkup yang sama yang akan membayangi kelas yang memiliki nama yang sama.
Johannes Schaub - litb

dalam 3.4.4 dikatakan mereka digunakan jika beberapa nama non-tipe menyembunyikan nama tipe. di situlah mereka paling sering digunakan, selain dari maju menyatakan seperti "kelas X;" (Ini dia satu-satunya yang merupakan deklarasi). itu berbicara tentang mereka di non-templat di sini. namun, 14.6 / 3 mencantumkan penggunaannya dalam templat.
Johannes Schaub - litb

1

Untuk VC, inilah ujian tentang penerusan deklarasi dan menentukan tipe yang mendasarinya:

  1. kode berikut dikompilasi ok.
    ketikkan int myint;
    enum T;
    batal foo (T * tp)
    {
        * tp = (T) 0x12345678;
    }
    enum T: char
    {
        SEBUAH
    };

Tetapi mendapat peringatan untuk / W4 (/ W3 tidak dikenakan peringatan ini)

peringatan C4480: ekstensi tidak standar yang digunakan: menentukan tipe yang mendasari untuk enum 'T'

  1. VC (Microsoft (R) 32-bit C / C ++ Mengoptimalkan Versi Kompiler 15.00.30729.01 untuk 80x86) terlihat bermasalah dalam kasus di atas:

    • saat melihat enum T; VC mengasumsikan enum tipe T menggunakan int standar 4 byte sebagai tipe yang mendasarinya, sehingga kode perakitan yang dihasilkan adalah:
    ? foo @@ YAXPAW4T @@@ Z PROC; foo
    ; File e: \ work \ c_cpp \ cpp_snippet.cpp
    ; Baris 13
        tekan ebp
        mov ebp, esp
    ; Baris 14
        mov eax, DWORD PTR _tp $ [ebp]
        mov DWORD PTR [eax], 305419896; 12345678H
    ; Baris 15
        pop ebp
        ret 0
    ? foo @@ YAXPAW4T @@@ Z ENDP; foo

Kode rakitan di atas diekstraksi dari /Fatest.asm secara langsung, bukan tebakan pribadi saya. Apakah Anda melihat mov DWORD PTR [eax], 305419896; Baris 12345678H?

cuplikan kode berikut membuktikannya:

    int main (int argc, char * argv)
    {
        Persatuan {
            char ca [4];
            Tt;
        }Sebuah;
        a.ca [0] = a.ca [1] = a. [ca [2] = a.ca [3] = 1;
        foo (& a.t);
        printf ("% # x,% # x,% # x,% # x \ n", a.ca [0], a.ca [1], a.ca [2], a.ca [3]) ;
        return 0;
    }

hasilnya adalah: 0x78, 0x56, 0x34, 0x12

  • setelah menghapus deklarasi maju enum T dan pindahkan definisi fungsi foo setelah definisi enum T: hasilnya OK:

instruksi kunci di atas menjadi:

mov BYTE PTR [eax], 120; 00000078H

hasil akhirnya adalah: 0x78, 0x1, 0x1, 0x1

Perhatikan nilainya tidak ditimpa

Jadi penggunaan maju-deklarasi enum di VC dianggap berbahaya.

BTW, tidak mengherankan, sintaks untuk deklarasi tipe yang mendasarinya sama dengan di C #. Dalam praktiknya saya menemukan itu layak untuk menyimpan 3 byte dengan menentukan tipe yang mendasarinya sebagai char ketika berbicara dengan sistem tertanam, yang merupakan memori terbatas.


1

Dalam proyek saya, saya mengadopsi teknik Namespace-Bound Enumeration untuk menangani enumkomponen warisan dan pihak ketiga. Berikut ini sebuah contoh:

forward.h:

namespace type
{
    class legacy_type;
    typedef const legacy_type& type;
}

enum.h:

// May be defined here or pulled in via #include.
namespace legacy
{
    enum evil { x , y, z };
}


namespace type
{
    using legacy::evil;

    class legacy_type
    {
    public:
        legacy_type(evil e)
            : e_(e)
        {}

        operator evil() const
        {
            return e_;
        }

    private:
        evil e_;
    };
}

foo.h:

#include "forward.h"

class foo
{
public:
    void f(type::type t);
};

foo.cc:

#include "foo.h"

#include <iostream>
#include "enum.h"

void foo::f(type::type t)
{
    switch (t)
    {
        case legacy::x:
            std::cout << "x" << std::endl;
            break;
        case legacy::y:
            std::cout << "y" << std::endl;
            break;
        case legacy::z:
            std::cout << "z" << std::endl;
            break;
        default:
            std::cout << "default" << std::endl;
    }
}

main.cc:

#include "foo.h"
#include "enum.h"

int main()
{
    foo fu;
    fu.f(legacy::x);

    return 0;
}

Perhatikan bahwa foo.htajuk tidak harus tahu apa-apa legacy::evil. Hanya file yang menggunakan tipe lawas legacy::evil(di sini: main.cc) yang perlu disertakan enum.h.


0

Solusi saya untuk masalah Anda adalah:

1 - gunakan int bukan enum: Deklarasikan int Anda di ruang nama anonim di file CPP Anda (bukan di header):

namespace
{
   const int FUNCTIONALITY_NORMAL = 0 ;
   const int FUNCTIONALITY_RESTRICTED = 1 ;
   const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
}

Karena metode Anda bersifat pribadi, tidak ada yang akan mengacaukan data. Anda bahkan dapat melangkah lebih jauh untuk menguji apakah seseorang mengirimi Anda data yang tidak valid:

namespace
{
   const int FUNCTIONALITY_begin = 0 ;
   const int FUNCTIONALITY_NORMAL = 0 ;
   const int FUNCTIONALITY_RESTRICTED = 1 ;
   const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
   const int FUNCTIONALITY_end = 3 ;

   bool isFunctionalityCorrect(int i)
   {
      return (i >= FUNCTIONALITY_begin) && (i < FUNCTIONALITY_end) ;
   }
}

2: buat kelas penuh dengan instantiations const terbatas, seperti yang dilakukan di Jawa. Teruskan mendeklarasikan kelas, dan kemudian mendefinisikannya dalam file CPP, dan hanya instanciate nilai-nilai seperti enum. Saya melakukan sesuatu seperti itu di C ++, dan hasilnya tidak memuaskan seperti yang diinginkan, karena memerlukan beberapa kode untuk mensimulasikan enum (copy konstruksi, operator =, dll).

3: Seperti yang diusulkan sebelumnya, gunakan enum yang dideklarasikan secara pribadi. Terlepas dari kenyataan bahwa pengguna akan melihat definisi lengkapnya, ia tidak akan dapat menggunakannya, atau menggunakan metode pribadi. Jadi, Anda biasanya dapat memodifikasi enum dan konten metode yang ada tanpa perlu mengkompilasi ulang kode menggunakan kelas Anda.

Dugaan saya akan menjadi solusi 3 atau 1.


-1

Karena enum dapat menjadi ukuran integral dari berbagai ukuran (kompiler memutuskan ukuran yang dimiliki oleh enum), penunjuk ke enum juga dapat memiliki ukuran yang bervariasi, karena ini adalah tipe integral (karakter memiliki pointer dari ukuran yang berbeda pada beberapa platform). misalnya).

Jadi kompiler bahkan tidak dapat membiarkan Anda meneruskan-mendeklarasikan enum dan pengguna pointer ke sana, karena bahkan di sana, ia membutuhkan ukuran enum.


-1

Anda menentukan enumerasi untuk membatasi nilai elemen elemen yang mungkin untuk kumpulan terbatas. Pembatasan ini harus diberlakukan pada waktu kompilasi.

Ketika meneruskan menyatakan fakta bahwa Anda akan menggunakan 'set terbatas' nanti tidak menambah nilai: kode selanjutnya perlu mengetahui nilai yang mungkin untuk mendapatkan manfaat darinya.

Meskipun compiler yang bersangkutan tentang ukuran tipe enumerasi, yang maksud dari pencacahan akan hilang ketika Anda maju menyatakan hal itu.


1
Tidak, kode selanjutnya tidak perlu tahu nilai-nilai ini berguna - khususnya, jika kode berikutnya hanyalah prototipe fungsi yang mengambil atau mengembalikan parameter enum, ukuran tipe tidak penting. Menggunakan deklarasi maju di sini dapat menghapus dependensi build, mempercepat kompilasi.
j_random_hacker

Kamu benar. Tujuannya bukan untuk mematuhi nilai-nilai, tetapi tipe. Diselesaikan dengan 0x jenis Enum.
xtofl
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.