it-swarm-id.com

Teruskan mendeklarasikan enum dalam C ++

Saya mencoba melakukan sesuatu seperti berikut:

enum E;

void Foo(E e);

enum E {A, B, C};

yang ditolak kompilator. Saya telah 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.

253
szevvy

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

Tipe dasar dari suatu enumerasi adalah tipe integral yang dapat mewakili semua nilai enumerator yang didefinisikan dalam enumerasi. Ini adalah implementasi yang 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 unsigned int. Jika enumerator-list kosong, tipe yang mendasari adalah seolah-olah enumerator memiliki enumerator tunggal dengan nilai 0. Nilai sizeof() diterapkan pada enumerasi type, objek tipe enumerasi, atau enumerator, adalah nilai sizeof() yang 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

206
KJAWolf

Deklarasi maju enum juga dimungkinkan dalam C++ 0x. 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.
188
user119017

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++0x atau -std=c++11 dalam 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.

73
Tom

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

Tapi bisakah Anda meneruskan mendeklarasikan enum dalam C++?

Tidak, kamu tidak bisa.

Tapi mengapa tidak mengizinkannya? Jika diizinkan, Anda dapat menentukan jenis enum Anda di file header Anda, dan nilai-nilai enum Anda di file sumber Anda. Kedengarannya seperti itu harus diizinkan kan?

Salah.

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

Apa artinya?

Ini berarti bahwa tipe yang mendasari enum Anda tidak dapat sepenuhnya ditentukan sampai Anda memiliki semua nilai dari enum yang ditentukan. Man yang mana Anda tidak dapat memisahkan deklarasi dan definisi enum Anda. Dan karena itu Anda tidak dapat meneruskan mendeklarasikan enum dalam 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 yang 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 unsigned int. 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 sizeof() diterapkan ke tipe yang mendasarinya.

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

Bagaimana jika Anda menentukan jenis enum Anda secara eksplisit seperti ini:

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

Bisakah Anda meneruskan menyatakan enum Anda?

Tapi mengapa tidak?

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

Sumber

30
Brian R. Bondy

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

Penerusan mendeklarasikan enum adalah tidak standar, karena pointer ke tipe enum yang berbeda tidak dijamin dengan ukuran yang sama. Kompiler 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.

13
James Hopkin

Memang tidak ada yang namanya deklarasi maju enum. Karena definisi enum tidak mengandung kode apa pun yang dapat 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 kelas yang nyata, sepenuhnya tersembunyi, mengimplementasikan antarmuka (mewarisi) antarmuka. 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.

7

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 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 Standar C++ berikutnya karena tahun depan memungkinkan yang berikut, yang meyakinkan saya masalah sebenarnya telah berkaitan dengan jenis yang mendasarinya:

enum X : int;

Itu 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.2 dalam konsep kerja saat ini.

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 bahwa Econtent mengkompilasi ke panjang, sehingga dapat dipertukarkan dengan E.

4
Laurie Cheers

Sepertinya tidak bisa dideklarasikan ke depan dalam GCC!

Diskusi yang menarik di sini

2
prakash

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

2
Leszek Swirski

Jika Anda benar-benar tidak ingin enum Anda muncul di file header Anda DAN memastikan bahwa itu hanya digunakan oleh 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.

2
Vincent Robert

Dalam proyek saya, saya mengadopsi teknik Namespace-Bound Enumeration untuk menangani enums dari komponen lawas 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 header foo.h tidak perlu tahu apa-apa tentang legacy::evil. Hanya file yang menggunakan tipe lawas legacy::evil (di sini: main.cc) perlu menyertakan enum.h.

1
mavam

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

  1. kode berikut dikompilasi ok.
 typedef int myint; 
 enum T; 
 void foo (T * tp) 
 {
 * tp = (T) 0x12345678; 
} 
 enum T: char 
 {
 A 
}; 

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 tipe enum T menggunakan int standar 4 byte sebagai tipe yang mendasarinya, sehingga kode Majelis 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 Majelis di atas diekstrak 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) 
 {
 union {
 char ca [4]; 
 Tt; 
} a; 
 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 deklarasi maju enum dalam 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
zhaorufei

Ada beberapa perbedaan pendapat karena ini terbentur (semacam), jadi inilah beberapa bit yang relevan dari standar. Penelitian menunjukkan bahwa standar 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 masuk ke int atau int yang tidak ditandatangani. 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 tipe yang mendasari enum adalah implementasi-didefinisikan, dengan satu pembatasan kecil.

Selanjutnya kita beralih ke bagian pada "tipe tidak lengkap" (3,9), yaitu hampir sedekat yang kita dapatkan 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. Tipe pointer ke array dengan ukuran yang tidak diketahui, atau tipe yang didefinisikan oleh deklarasi typedef menjadi array dengan ukuran yang tidak diketahui, tidak dapat diselesaikan.

Jadi di sana, 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 direferensikan dalam situasi menurut nilai, dan kompilator memang perlu mengetahui ukuran penyimpanan dalam situasi tersebut. Karena ukuran penyimpanan ditentukan oleh implementasi, 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.

1
Dan Olson

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 (menyalin konstruksi, operator =, dll).

3: Seperti yang diusulkan sebelumnya, gunakan enum yang dinyatakan 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.

0
paercebal