ส่งต่อประกาศ enum ใน C ++


264

ฉันกำลังพยายามทำสิ่งต่อไปนี้:

enum E;

void Foo(E e);

enum E {A, B, C};

ซึ่งคอมไพเลอร์ปฏิเสธ ฉันได้ดู Google อย่างรวดเร็วและฉันทามติดูเหมือนว่า "คุณทำไม่ได้" แต่ฉันไม่เข้าใจว่าทำไม มีใครอธิบายได้บ้าง

ความกระจ่างที่ 2: ฉันกำลังทำสิ่งนี้เนื่องจากฉันมีวิธีการส่วนตัวในชั้นเรียนที่กล่าวว่า enum และฉันไม่ต้องการให้คุณค่าของ enum เปิดเผยดังนั้นตัวอย่างเช่นฉันไม่ต้องการให้ใครรู้ว่า E ถูกกำหนดเป็น

enum E {
    FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X
}

เนื่องจากโครงการ X ไม่ใช่สิ่งที่ฉันต้องการให้ผู้ใช้ของฉันรู้

ดังนั้นฉันต้องการส่งต่อประกาศ enum เพื่อให้ฉันสามารถใส่วิธีการส่วนตัวในไฟล์ส่วนหัวประกาศ enum ภายใน cpp และแจกจ่ายไฟล์ไลบรารีที่สร้างขึ้นและส่วนหัวให้กับผู้คน

สำหรับคอมไพเลอร์ - มันคือ GCC


หลายปีที่ผ่านมาในเรื่องนี้และอย่างใด StackOverflow ล่อลวงฉันกลับ;) ในฐานะที่เป็นข้อเสนอแนะชันสูตรศพ - เพียงแค่ไม่ทำเช่นนี้ โดยเฉพาะในสถานการณ์ที่คุณอธิบาย ฉันต้องการที่จะกำหนดอินเทอร์เฟซที่เป็นนามธรรมและเปิดเผยทีโอทีนี้ที่เขาเป็นผู้ใช้และเก็บคำจำกัดความของ enum และรายละเอียดการใช้งานอื่น ๆ ทั้งหมดไว้กับการติดตั้งภายในที่ไม่มีใครเห็นอยู่ข้างๆ สิ่งใด
RnR

คำตอบ:


217

เหตุผลที่ enum ไม่สามารถส่งต่อได้คือการที่ไม่ทราบค่าคอมไพเลอร์ไม่สามารถทราบที่เก็บข้อมูลที่จำเป็นสำหรับตัวแปร enum คอมไพเลอร์ของ C ++ ได้รับอนุญาตให้ระบุพื้นที่เก็บข้อมูลจริงตามขนาดที่จำเป็นในการเก็บค่าทั้งหมดที่ระบุ หากสิ่งที่มองเห็นได้คือการประกาศไปข้างหน้าหน่วยการแปลไม่สามารถรู้ได้ว่าจะเลือกขนาดหน่วยเก็บข้อมูลใด - อาจเป็นรูปแบบ char หรือแบบ int หรืออย่างอื่น


จากมาตรา 7.2.5 ของมาตรฐาน ISO C ++:

ประเภทพื้นฐานของการแจงนับเป็นประเภทหนึ่งที่สามารถเป็นตัวแทนค่าการแจงนับที่กำหนดไว้ในการแจงนับ มันคือการดำเนินการกำหนดชนิดหนึ่งที่ใช้เป็นชนิดพื้นฐานสำหรับการแจงนับยกเว้นว่าประเภทพื้นฐานจะต้องไม่ใหญ่กว่าintเว้นแต่ค่าของแจงนับไม่สามารถใส่ในหรือint unsigned intถ้ารายการตัวแจงนับว่างเปล่าชนิดที่อยู่ภายใต้จะเหมือนกับว่าการแจงนับมีตัวแจงนับเดียวที่มีค่า 0 ค่าที่sizeof()ใช้กับชนิดการแจงนับวัตถุของชนิดการแจงนับหรือการแจงนับเป็นค่าที่sizeof()ใช้กับ ประเภทพื้นฐาน

เนื่องจากผู้เรียกใช้ฟังก์ชันต้องทราบขนาดของพารามิเตอร์เพื่อตั้งค่า call stack อย่างถูกต้องจึงต้องทราบจำนวนการแจกแจงในรายการการแจงนับก่อนฟังก์ชันต้นแบบ

อัปเดต: ใน C ++ 0X มีการเสนอและยอมรับไวยากรณ์สำหรับการประกาศประเภท enum คุณสามารถดูข้อเสนอได้ที่http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf


29
-1 เหตุผลของคุณไม่ถูกต้อง - มิฉะนั้นคุณได้รับอนุญาตให้ส่งต่อประกาศ "class C;" จากนั้นประกาศฟังก์ชั่นต้นแบบที่รับหรือคืนค่า C ก่อนกำหนด C อย่างสมบูรณ์
j_random_hacker

112
@j_random: คุณไม่สามารถใช้คลาสได้ก่อนที่จะถูกกำหนดอย่างสมบูรณ์ - คุณสามารถใช้ตัวชี้หรือการอ้างอิงถึงคลาสนั้นได้และนั่นเป็นเพราะขนาดและวิธีการทำงานของมันไม่ได้ขึ้นอยู่กับคลาส
RnR

27
ขนาดของการอ้างอิงหรือตัวชี้ไปยังวัตถุคลาสที่ถูกกำหนดโดยคอมไพเลอร์และเป็นอิสระจากขนาดที่แท้จริงของวัตถุ - มันเป็นขนาดของตัวชี้และการอ้างอิง enum เป็นวัตถุและขนาดของมันจำเป็นสำหรับคอมไพเลอร์ในการเข้าถึงที่เก็บข้อมูลที่ถูกต้อง
KJAWolf

16
ตามหลักเหตุผลมันจะสามารถประกาศพอยน์เตอร์ / การอ้างอิงถึง enums ได้ถ้าเรามีการบอกล่วงหน้า enums เช่นเดียวกับที่เราทำได้กับคลาส มันเป็นเพียงแค่ว่าคุณไม่ได้มักจะจัดการกับตัวชี้ไปยัง enums :)
พาเวล Minaev

20
ฉันรู้ว่าการสนทนานี้จบมานานแล้ว แต่ฉันต้องติดต่อกับ @j_random_hacker ที่นี่: ปัญหาที่นี่ไม่ได้เกี่ยวกับตัวชี้หรือการอ้างอิงถึงประเภทที่ไม่สมบูรณ์ แต่เกี่ยวกับการใช้ประเภทที่ไม่สมบูรณ์ในการประกาศ เนื่องจากเป็นสิ่งที่ถูกต้องตามกฎหมายstruct S; void foo(S s);(โปรดทราบว่าfooมีการประกาศเท่านั้นไม่ได้กำหนดไว้) ดังนั้นจึงไม่มีเหตุผลที่เราจะไม่สามารถทำได้enum E; void foo(E e);เช่นกัน ในทั้งสองกรณีขนาดไม่จำเป็นต้องใช้
Luc Touraille

199

การประกาศล่วงหน้าของ enums เป็นไปได้ตั้งแต่ C ++ 11 ก่อนหน้านี้เหตุผลที่ไม่สามารถประกาศล่วงหน้าได้เนื่องจากขนาดของการแจงนับขึ้นอยู่กับเนื้อหา ตราบใดที่ขนาดของการแจงนับถูกระบุโดยแอปพลิเคชันมันสามารถประกาศไปข้างหน้าได้:

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
คอมไพเลอร์รองรับคุณสมบัตินี้หรือไม่? GCC 4.5 ดูเหมือนจะไม่มี :(
rubenvb

4
@rubenvb ดังนั้น Visual C ++ 11 (2012) blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx
ปิดใช้งาน

ฉันกำลังมองหา enum32_t และด้วยคำตอบของคุณ enum XXX: uint32_t {a, b, c};
fantastory

ฉันคิดว่า enums ที่กำหนดขอบเขต (คลาส enum) ถูกนำไปใช้ใน C ++ 11 ถ้าเป็นเช่นนั้นจะถูกกฎหมายใน C ++ 0X อย่างไร
Terrabits

1
C ++ 0x เป็นชื่อที่ใช้งานได้ของ C ++ 11, @Terrabits ก่อนที่มันจะเป็นมาตรฐานอย่างเป็นทางการ เหตุผลก็คือว่าถ้าเป็นที่รู้จักกันในสถานที่ (หรือมีแนวโน้มสูง) รวมอยู่ในมาตรฐานการปรับปรุงการใช้งานของคุณลักษณะนั้นก่อนที่จะมีการปล่อยตัวมาตรฐานอย่างเป็นทางการมีแนวโน้มที่จะใช้ชื่อการทำงาน (เช่นคอมไพเลอร์ที่รองรับฟีเจอร์ C ++ 11 ก่อนมาตรฐานอย่างเป็นทางการในปี 2011 มีการสนับสนุน C ++ 0x คอมไพเลอร์ที่รองรับฟีเจอร์ C ++ 17 ก่อนที่มาตรฐานอย่างเป็นทางการจะมีการสนับสนุน C ++ 1z และคอมไพเลอร์ที่รองรับคุณสมบัติ C ++ 20 ตอนนี้ (2019) มีการสนับสนุน C ++ 2a)
Justin Time - Reinstate Monica

79

ฉันกำลังเพิ่มคำตอบล่าสุดที่นี่เนื่องจากได้รับการพัฒนาล่าสุด

คุณสามารถส่งต่อประกาศ enum ใน C ++ 11 ได้ตราบใดที่คุณประกาศประเภทที่จัดเก็บในเวลาเดียวกัน ไวยากรณ์มีลักษณะดังนี้:

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

....

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

ในความเป็นจริงถ้าฟังก์ชั่นไม่เคยอ้างถึงค่าของการแจงนับคุณไม่จำเป็นต้องมีการประกาศที่สมบูรณ์ ณ จุดนั้น

รองรับ G ++ 4.6 ขึ้นไป ( -std=c++0xหรือ-std=c++11ในรุ่นที่ใหม่กว่า) Visual C ++ 2013 รองรับสิ่งนี้; ในรุ่นก่อนหน้านี้มีการสนับสนุนที่ไม่ได้มาตรฐานบางอย่างที่ฉันยังไม่ได้หามาเลย - ฉันพบข้อเสนอแนะว่าการประกาศล่วงหน้าอย่างง่าย ๆ นั้นถูกกฎหมาย แต่ YMMV


4
+1 เพราะนี่เป็นคำตอบเดียวที่กล่าวถึงคุณต้องประกาศประเภทในการประกาศของคุณเช่นเดียวกับคำจำกัดความของคุณ
turoni

ฉันเชื่อว่าการสนับสนุนบางส่วนในช่วงต้น MSVC ได้รับการสนับสนุนจาก C ++ / CLI enum classเป็นส่วนขยาย C ++ (ก่อนที่จะแตกต่างจาก C ++ 11 enum class) อย่างน้อยถ้าฉันจำได้อย่างถูกต้อง คอมไพเลอร์อนุญาตให้คุณระบุประเภทพื้นฐานของ enum แต่ไม่สนับสนุนenum classหรือระบุล่วงหน้าล่วงหน้าและเตือนคุณว่าการระบุตัวระบุที่มีขอบเขตของ enum นั้นเป็นส่วนขยายที่ไม่ได้มาตรฐาน ฉันจำได้ว่ามันใช้งานได้ประมาณเดียวกับการระบุประเภทที่แฝงอยู่ใน C ++ 11 แต่ยกเว้นว่าน่ารำคาญกว่าเพราะคุณต้องระงับคำเตือน
Justin Time - Reinstate Monica

30

ส่งต่อประกาศสิ่งที่อยู่ใน C ++ เป็นประโยชน์อย่างมากเพราะมันอย่างมากเพิ่มความเร็วในเวลารวบรวม คุณสามารถประกาศไปข้างหน้าหลายสิ่งหลายอย่างใน C ++ รวมไปถึง: struct, class, functionฯลฯ ...

แต่คุณสามารถส่งต่อประกาศเป็นenumC ++ ได้ไหม

ไม่คุณไม่สามารถ

แต่ทำไมไม่อนุญาต หากได้รับอนุญาตคุณสามารถกำหนดenumประเภทของคุณในไฟล์ส่วนหัวและenumค่าของคุณในไฟล์ต้นฉบับ ดูเหมือนว่าควรได้รับอนุญาตใช่ไหม

ไม่ถูกต้อง.

ใน C ++ ไม่มีประเภทเริ่มต้นสำหรับenumเช่นใน C # (int) ใน C ++ ของคุณชนิดจะถูกกำหนดโดยคอมไพเลอร์ที่จะมีวิธีใดที่จะพอดีกับช่วงของค่าที่คุณมีสำหรับคุณenumenum

นั่นหมายความว่าอย่างไร?

หมายความว่าenumประเภทพื้นฐานของคุณไม่สามารถระบุได้อย่างเต็มที่จนกว่าคุณจะมีค่าทั้งหมดที่enumกำหนดไว้ enumซึ่งชายคุณไม่สามารถแยกการประกาศและความหมายของคุณ ดังนั้นคุณไม่สามารถส่งต่อการประกาศenumใน C ++

มาตรฐาน ISO C ++ S7.2.5:

ประเภทพื้นฐานของการแจงนับเป็นประเภทหนึ่งที่สามารถเป็นตัวแทนของค่าแจงนับทั้งหมดที่กำหนดไว้ในการแจงนับ มันคือการดำเนินการกำหนดชนิดหนึ่งที่ใช้เป็นชนิดพื้นฐานสำหรับการแจงนับยกเว้นว่าประเภทพื้นฐานจะต้องไม่ใหญ่กว่าintเว้นแต่ค่าของแจงนับไม่สามารถใส่ในหรือint unsigned intถ้ารายการตัวแจงนับว่างเปล่าชนิดที่อยู่ภายใต้จะเหมือนกับว่าการแจงนับมีตัวแจงนับเดียวที่มีค่า 0 ค่าที่sizeof()ใช้กับชนิดการแจงนับวัตถุของชนิดการแจงนับหรือการแจงนับเป็นค่าที่sizeof()ใช้กับ ประเภทพื้นฐาน

คุณสามารถกำหนดขนาดของประเภทที่ระบุใน C ++ โดยใช้sizeofโอเปอเรเตอร์ ขนาดของประเภทที่แจกแจงคือขนาดของประเภทต้นแบบ ด้วยวิธีนี้คุณสามารถเดาได้ว่าคอมไพเลอร์ชนิดใดที่คุณใช้enumอยู่

ถ้าคุณระบุประเภทของคุณenumอย่างชัดเจนเช่นนี้:

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

จากนั้นคุณสามารถส่งต่อประกาศของคุณenum?

ไม่ แต่ทำไมล่ะ

การระบุชนิดของ an enumไม่ใช่ส่วนหนึ่งของมาตรฐาน C ++ ปัจจุบัน มันเป็นนามสกุล VC ++ มันจะเป็นส่วนหนึ่งของ C ++ 0x

แหล่ง


14
คำตอบนี้ได้หลายปีแล้ว
ทอม

เวลาทำให้คนโง่เขลาทั้งหมด ความคิดเห็นของคุณตอนนี้เป็นเวลาหลายปีที่ล้าสมัย; คำตอบหนึ่งทศวรรษ!
pjcard

14

[คำตอบของฉันผิด แต่ฉันทิ้งไว้ที่นี่เพราะความคิดเห็นมีประโยชน์]

การประกาศล่วงหน้า enums ไม่ได้มาตรฐานเนื่องจากตัวชี้ไปยังประเภท enum ต่าง ๆ ไม่รับประกันว่าจะมีขนาดเท่ากัน คอมไพเลอร์อาจต้องดูคำจำกัดความเพื่อทราบว่าพอยน์เตอร์ขนาดใดที่สามารถใช้กับประเภทนี้

ในทางปฏิบัติอย่างน้อยในคอมไพเลอร์ที่ได้รับความนิยมพอยน์เตอร์ถึง enums นั้นมีขนาดที่สอดคล้องกัน ยกตัวอย่างเช่นการประกาศล่วงหน้าของ enums เป็นส่วนขยายภาษาโดย Visual C ++


2
-1 หากการใช้เหตุผลของคุณถูกต้องการใช้เหตุผลแบบเดียวกันก็บอกเป็นนัยว่าไม่สามารถใช้การประกาศไปข้างหน้าของประเภทชั้นเรียนเพื่อสร้างพอยน์เตอร์ให้กับประเภทเหล่านั้น - แต่สามารถทำได้
j_random_hacker

6
+1 การใช้เหตุผลนั้นถูกต้อง กรณีเฉพาะคือแพลตฟอร์มที่ sizeof (char *)> sizeof (int *) ทั้งสองสามารถเป็นประเภทพื้นฐานสำหรับ enum ขึ้นอยู่กับช่วง คลาสไม่มีประเภทพื้นฐานดังนั้นการเปรียบเทียบจึงเป็นเท็จ
MSalters

3
@MSalters: ตัวอย่าง: "struct S {int x;};" ตอนนี้ sizeof (S *) ต้องเท่ากับขนาดของตัวชี้ไปยังโครงสร้างอื่นเนื่องจาก C ++ อนุญาตให้ตัวชี้ดังกล่าวถูกประกาศและใช้งานก่อนที่คำจำกัดความของ S ...
j_random_hacker

1
@MSalters: ... บนแพลตฟอร์มที่ sizeof (char *)> sizeof (int *) การใช้ตัวชี้ "full-size" สำหรับ struct เฉพาะนี้อาจไม่มีประสิทธิภาพ แต่มันลดความซับซ้อนของการเข้ารหัสลงอย่างมาก - สิ่งที่สามารถและควรจะทำสำหรับประเภท enum
j_random_hacker

4
ตัวชี้ไปยังข้อมูลและตัวชี้ไปยังฟังก์ชั่นอาจมีขนาดแตกต่างกัน แต่ฉันค่อนข้างแน่ใจว่าตัวชี้ข้อมูลต้องไปกลับ (ส่งไปยังประเภทตัวชี้ข้อมูลอื่นแล้วกลับไปที่เดิมต้องยังคงทำงาน) ซึ่งหมายความว่า ตัวชี้ข้อมูลมีขนาดเท่ากัน
Ben Voigt

7

แน่นอนไม่มีสิ่งเช่นการประกาศล่วงหน้าของ enum เนื่องจากคำจำกัดความของ enum ไม่ได้มีรหัสใด ๆ ที่อาจขึ้นอยู่กับรหัสอื่น ๆ ที่ใช้ enum ดังนั้นจึงไม่มีปัญหาในการกำหนด enum อย่างสมบูรณ์เมื่อคุณประกาศครั้งแรก

ถ้าการใช้ enum ของคุณเพียงอย่างเดียวคือการทำหน้าที่สมาชิกส่วนตัวคุณสามารถใช้การห่อหุ้มด้วยการให้ Enum เป็นสมาชิกส่วนตัวของคลาสนั้นได้ enum ยังคงต้องถูกกำหนดอย่างสมบูรณ์ ณ จุดประกาศนั่นคือภายในนิยามคลาส อย่างไรก็ตามนี่ไม่ใช่ปัญหาที่ใหญ่กว่าในการประกาศฟังก์ชั่นสมาชิกส่วนตัวที่นั่นและไม่ได้เป็นการแสดงถึงการดำเนินงานภายในที่เลวร้ายไปกว่านั้น

หากคุณต้องการการซ่อนเร้นในระดับลึกสำหรับรายละเอียดการใช้งานของคุณคุณสามารถแบ่งมันเป็นอินเทอร์เฟซที่เป็นนามธรรมซึ่งประกอบด้วยฟังก์ชั่นเสมือนจริงเท่านั้นและการปกปิดอินเทอร์เฟซสำหรับชั้นเรียน การสร้างอินสแตนซ์ของคลาสสามารถจัดการได้โดยโรงงานหรือฟังก์ชันสมาชิกแบบคงที่ของอินเทอร์เฟซ ด้วยวิธีนี้แม้ชื่อจริงจะไม่ถูกเปิดเผยก็ตาม


5

เพียงแค่สังเกตว่าเหตุผลก็คือขนาดของ enum ยังไม่เป็นที่รู้จักหลังจากการประกาศล่วงหน้า ทีนี้คุณใช้การประกาศไปข้างหน้าของ struct เพื่อให้สามารถส่งตัวชี้ไปรอบ ๆ หรืออ้างอิงวัตถุจากสถานที่ที่อ้างถึงในคำนิยามการประกาศไปข้างหน้าด้วยเช่นกัน

ส่งต่อประกาศ enum จะไม่เป็นประโยชน์มากเกินไปเพราะใครอยากจะสามารถผ่านรอบ Enum โดยค่า คุณไม่สามารถมีตัวชี้ได้เพราะเมื่อเร็ว ๆ นี้ฉันได้รับแจ้งว่าบางแพลตฟอร์มใช้พอยน์เตอร์ที่มีขนาดแตกต่างกันสำหรับ char มากกว่าสำหรับ int หรือยาว ดังนั้นทุกอย่างขึ้นอยู่กับเนื้อหาของ enum

มาตรฐาน C ++ ปัจจุบันปฏิเสธการทำสิ่งที่ชอบอย่างชัดเจน

enum X;

(ใน7.1.5.3/1) แต่ต่อไป c ++ มาตรฐานเนื่องจากในปีถัดไปจะช่วยให้ต่อไปนี้ซึ่งเชื่อฉันปัญหาจริงมีจะทำอย่างไรกับชนิดพื้นฐาน:

enum X : int;

มันเป็นที่รู้จักในฐานะการประกาศ enum "ทึบแสง" คุณสามารถใช้ X ตามค่าในรหัสต่อไปนี้ และตัวแจงนับสามารถระบุได้ในภายหลังการประกาศการแจงนับภายหลัง ดู7.2ในร่างการทำงานปัจจุบัน


4

ฉันจะทำแบบนี้:

[ในส่วนหัวสาธารณะ]

typedef unsigned long E;

void Foo(E e);

[ในส่วนหัวภายใน]

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

ด้วยการเพิ่ม FORCE_32BIT เรามั่นใจว่า Econtent รวบรวมไปเป็นเวลานานดังนั้นจึงสามารถใช้แทนกันได้กับ E


1
แน่นอนนี่หมายความว่า (A) ประเภทของ E และ Econtent แตกต่างกันและ (B) ในระบบ LP64, sizeof (E) = 2 * sizeof (EContent) การแก้ไขเล็กน้อย: ULONG_MAX อ่านง่ายขึ้นเช่นกัน
MSalters

2

หากคุณไม่ต้องการให้ enum ของคุณปรากฏในไฟล์ส่วนหัวและตรวจสอบให้แน่ใจว่ามันถูกใช้โดยวิธีส่วนตัวเท่านั้นวิธีแก้ปัญหาหนึ่งก็คือการใช้หลักการ pimpl

เป็นเทคนิคที่รับรองว่าจะซ่อนชั้นเรียนไว้ในหัวโดยเพียงแค่ประกาศ:

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

จากนั้นในไฟล์การนำไปใช้ (cpp) ของคุณคุณประกาศคลาสที่จะเป็นตัวแทนของ internals

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

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

คุณต้องสร้างการใช้งานแบบไดนามิกในตัวสร้างคลาสและลบทิ้งใน destructor และเมื่อใช้เมธอดสาธารณะคุณต้องใช้:

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

มีข้อดีสำหรับการใช้ pimpl หนึ่งคือมันแยกส่วนหัวคลาสของคุณจากการใช้งานไม่จำเป็นต้องคอมไพล์คลาสอื่น ๆ เมื่อเปลี่ยนการใช้งานคลาสหนึ่ง อีกอย่างคือความเร็วในการรวบรวมของคุณเพิ่มขึ้นเพราะส่วนหัวของคุณนั้นเรียบง่าย

แต่มันเป็นความเจ็บปวดที่จะใช้ดังนั้นคุณควรถามตัวเองว่าการประกาศ enum ของคุณในฐานะส่วนบุคคลนั้นเป็นปัญหาหรือไม่


3
struct AImpl; struct A {ส่วนตัว: AImpl * pImpl; };

2

คุณสามารถล้อม enum ใน struct เพิ่มในคอนสตรัคเตอร์และการแปลงประเภทและส่งต่อประกาศโครงสร้างแทน

#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; \
}

สิ่งนี้ดูเหมือนจะใช้งานได้: http://ideone.com/TYtP2



1

มีความขัดแย้งตั้งแต่ถูกชน (เรียงลำดับ) ดังนั้นนี่คือบิตที่เกี่ยวข้องจากมาตรฐาน การวิจัยแสดงให้เห็นว่ามาตรฐานไม่ได้กำหนดการประกาศล่วงหน้าจริง ๆ และไม่ได้ระบุอย่างชัดเจนว่า enums สามารถหรือไม่สามารถประกาศล่วงหน้าได้

ก่อนอื่นจาก dcl.enum หัวข้อ 7.2:

ประเภทพื้นฐานของการแจงนับเป็นประเภทหนึ่งที่สามารถเป็นตัวแทนของค่าแจงนับทั้งหมดที่กำหนดไว้ในการแจงนับ มันคือการดำเนินการที่กำหนดซึ่งเป็นประเภทหนึ่งที่ใช้เป็นประเภทพื้นฐานสำหรับการแจกแจงยกเว้นว่าประเภทพื้นฐานจะต้องไม่ใหญ่กว่า int เว้นแต่ค่าของตัวแจงนับไม่สามารถพอดีกับ int หรือไม่ได้ลงนาม int หากรายการตัวแจงนับว่างเปล่าชนิดพื้นฐานจะเหมือนกับว่าการแจงนับมีตัวแจงนับเดี่ยวที่มีค่า 0 ค่าของ sizeof () ที่ใช้กับชนิดการแจงนับวัตถุประเภทการแจงนับหรือการแจงนับเป็นค่าของ sizeof () นำไปใช้กับประเภทพื้นฐาน

ดังนั้นชนิดพื้นฐานของ enum จึงเป็นตัวกำหนดการนำไปใช้งานโดยมีข้อ จำกัด เล็กน้อยหนึ่งข้อ

ต่อไปเราจะกลับไปที่หัวข้อ "ประเภทที่ไม่สมบูรณ์" (3.9) ซึ่งใกล้เคียงที่สุดเท่าที่เราจะได้มาตรฐานในการประกาศไปข้างหน้า:

คลาสที่ได้รับการประกาศ แต่ไม่ได้กำหนดหรืออาร์เรย์ของขนาดที่ไม่รู้จักหรือประเภทองค์ประกอบที่ไม่สมบูรณ์เป็นประเภทวัตถุที่กำหนดไม่สมบูรณ์

ประเภทคลาส (เช่น "คลาส X") อาจไม่สมบูรณ์ในจุดหนึ่งในหน่วยการแปลและดำเนินการในภายหลัง ประเภท "class X" เป็นประเภทเดียวกันที่ทั้งสองจุด ชนิดที่ประกาศของวัตถุอาร์เรย์อาจเป็นอาร์เรย์ของประเภทคลาสที่ไม่สมบูรณ์และดังนั้นจึงไม่สมบูรณ์ หากประเภทคลาสเสร็จสมบูรณ์ในภายหลังในหน่วยการแปลประเภทอาร์เรย์จะเสร็จสมบูรณ์ ประเภทอาร์เรย์ที่จุดสองจุดนั้นเป็นประเภทเดียวกัน ชนิดที่ประกาศของวัตถุอาร์เรย์อาจเป็นขนาดที่ไม่รู้จักและดังนั้นจึงไม่สมบูรณ์ในจุดหนึ่งในหน่วยการแปลและเสร็จสมบูรณ์ในภายหลัง ประเภทอาเรย์ที่สองจุดเหล่านั้น ("อาเรย์ของขอบเขตที่ไม่รู้จักของ T" และ "อาเรย์ของ N T") เป็นชนิดที่แตกต่างกัน ชนิดของตัวชี้ไปยังอาร์เรย์ที่มีขนาดไม่รู้จักหรือชนิดที่กำหนดโดยการประกาศ typedef ให้เป็นอาร์เรย์ที่มีขนาดที่ไม่รู้จัก

ดังนั้นมาตรฐานจึงมีการวางประเภทที่สามารถประกาศไปข้างหน้าได้ Enum ไม่ได้อยู่ที่นั่นดังนั้นผู้เขียนคอมไพเลอร์โดยทั่วไปถือว่าการประกาศไปข้างหน้าว่าไม่ได้รับอนุญาตจากมาตรฐานเนื่องจากขนาดตัวแปรของประเภทพื้นฐาน

มันก็สมเหตุสมผลเช่นกัน Enums มักจะอ้างอิงในสถานการณ์ตามค่าและคอมไพเลอร์จะต้องรู้ขนาดของหน่วยเก็บในสถานการณ์เหล่านั้น เนื่องจากขนาดของหน่วยเก็บข้อมูลถูกกำหนดไว้ในการนำไปใช้งานคอมไพเลอร์หลายตัวอาจเลือกที่จะใช้ค่า 32 บิตสำหรับประเภทพื้นฐานของทุก enum ณ จุดที่มันเป็นไปได้ที่จะส่งต่อประกาศ การทดลองที่น่าสนใจอาจลองส่งประกาศ enum ใน visual studio จากนั้นบังคับให้ใช้ประเภทพื้นฐานที่ใหญ่กว่า sizeof (int) ตามที่อธิบายข้างต้นเพื่อดูว่าเกิดอะไรขึ้น


โปรดทราบว่ามันไม่อนุญาต "enum foo;" อย่างชัดเจน ใน 7.1.5.3/1 (แต่เช่นเดียวกับทุกสิ่งทุกอย่างตราบใดที่คอมไพเลอร์เตือนมันอาจจะยังคงรวบรวมรหัสดังกล่าวแน่นอน)
Johannes Schaub - litb

ขอบคุณที่ชี้ให้เห็นว่ามันเป็นย่อหน้าที่ลึกลับและอาจใช้เวลาหนึ่งสัปดาห์ในการวิเคราะห์ แต่มันก็ดีที่จะรู้ว่ามันอยู่ตรงนั้น
Dan Olson

ไม่ต้องกังวลย่อหน้ามาตรฐานบางย่อหน้านั้นน่าแปลกใจจริงๆ :) ทีนี้ตัวระบุประเภทที่ซับซ้อนนั้นเป็นสิ่งที่คุณระบุประเภท แต่ยังต้องระบุบางอย่างมากขึ้นเพื่อให้ชัดเจน เช่น "struct X" แทน "X" หรือ "enum Y" แทนที่จะเป็น "Y" เพียงอย่างเดียวคุณต้องยืนยันสิ่งที่เป็นประเภท
Johannes Schaub - litb

ดังนั้นคุณสามารถใช้มันได้เช่น: "class X * foo;" ถ้า X ยังไม่ได้ประกาศไปข้างหน้า หรือ "typename X :: foo" ในเทมเพลตสำหรับแก้ความกำกวม หรือ "class link obj;" หากมีฟังก์ชั่น "ลิงค์" ในขอบเขตเดียวกันที่จะทำให้คลาสมีชื่อเดียวกัน
Johannes Schaub - litb

ใน 3.4.4 มันบอกว่าพวกเขาจะใช้ถ้าบางชื่อที่ไม่ใช่ประเภทซ่อนชื่อประเภท นั่นคือสิ่งที่พวกเขาใช้บ่อยที่สุดนอกเหนือจากการประกาศล่วงหน้าเช่น "class X;" (นี่เป็นเพียงข้อแม้ของการประกาศเท่านั้น) มันพูดถึงพวกเขาในที่ไม่ใช่แม่แบบที่นี่ อย่างไรก็ตาม 14.6 / 3 แสดงรายการการใช้งานในเทมเพลต
Johannes Schaub - litb

1

สำหรับ VC ต่อไปนี้เป็นการทดสอบเกี่ยวกับการประกาศล่วงหน้าและการระบุประเภทพื้นฐาน:

  1. รหัสต่อไปนี้จะรวบรวมตกลง
    typedef int myint;
    Enum T;
    โมฆะ foo (T * tp)
    {
        * tp = (T) 0x12345678;
    }
    enum T: ตัวละคร
    {
        
    };

แต่ได้รับคำเตือนสำหรับ / W4 (/ W3 ไม่ได้รับคำเตือนนี้)

คำเตือน C4480: ส่วนขยายที่ไม่เป็นมาตรฐานที่ใช้: การระบุประเภทพื้นฐานสำหรับ enum 'T'

  1. VC (Microsoft (R) 32-bit C / C ++ การปรับแต่งคอมไพเลอร์เวอร์ชั่น 15.00.30729.01 สำหรับ 80x86) ดูบั๊กในกรณีข้างต้น:

    • เมื่อเห็น enum T; VC ถือว่า enum type T ใช้ค่าเริ่มต้น 4 ไบต์ int เป็นชนิดข้อมูลอ้างอิงดังนั้นรหัสแอสเซมบลีที่สร้างขึ้นคือ:
    ? foo @@ YAXPAW4T @@@ Z PROC; foo
    ; ไฟล์ e: \ work \ c_cpp \ cpp_snippet.cpp
    ; บรรทัดที่ 13
        ผลักดัน ebp
        mov ebp, esp
    ; บรรทัดที่ 14
        mov eax, DWORD PTR _tp $ [ebp]
        mov DWORD PTR [eax], 305419896; 12345678H
    ; บรรทัดที่ 15
        ป๊อปอัพ
        เกษียณ 0
    ? foo @@ YAXPAW4T @@@ Z ENDP; foo

รหัสการชุมนุมด้านบนถูกดึงมาจาก /Fest.asm โดยตรงไม่ใช่การเดาส่วนตัวของฉัน คุณเห็น mov DWORD PTR [eax], 305419896; เส้น 12345678H?

ข้อมูลโค้ดต่อไปนี้พิสูจน์ว่า:

    int หลัก (int argc, ถ่าน * argv)
    {
        ยูเนี่ยน {
            ถ่าน ca [4];
            T t;
        } ก;
        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]) ;
        กลับ 0
    }

ผลลัพธ์คือ: 0x78, 0x56, 0x34, 0x12

  • หลังจากลบการประกาศล่วงหน้าของ enum T และย้ายคำจำกัดความของฟังก์ชั่น foo หลังจากคำจำกัดความของ enum T: ผลลัพธ์คือ OK:

คำสั่งสำคัญข้างต้นกลายเป็น:

mov BYTE PTR [eax], 120; 00000078H

ผลลัพธ์สุดท้ายคือ: 0x78, 0x1, 0x1, 0x1

หมายเหตุค่าจะไม่ถูกเขียนทับ

ดังนั้นการใช้การประกาศล่วงหน้าของ enum ใน VC จึงถือว่าเป็นอันตราย

BTW จะไม่แปลกใจไวยากรณ์สำหรับการประกาศประเภทพื้นฐานเป็นเช่นเดียวกับใน C # ในบทสนทนาฉันพบว่ามันคุ้มค่าที่จะบันทึก 3 ไบต์โดยการระบุประเภทพื้นฐานเป็นอักขระเมื่อพูดคุยกับระบบฝังตัวซึ่งเป็นหน่วยความจำ จำกัด


1

ในโครงการของฉันฉันเลือกใช้Namespace-Bound Enumerationเพื่อจัดการกับenums จากมรดกและส่วนประกอบของบุคคลที่สาม นี่คือตัวอย่าง:

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;
}

โปรดสังเกตว่าส่วนหัวไม่ได้รู้อะไรเกี่ยวกับfoo.h legacy::evilเฉพาะแฟ้มที่ใช้ชนิดมรดกlegacy::evil(ที่นี่: main.cc) enum.hจำเป็นจะต้องรวม


0

ทางออกของปัญหาของฉันคือ:

1 - ใช้ int แทน enums: ประกาศ int ของคุณในเนมสเปซที่ไม่ระบุชื่อในไฟล์ CPP ของคุณ (ไม่ใช่ในส่วนหัว):

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

เนื่องจากวิธีการของคุณเป็นแบบส่วนตัวไม่มีใครจะยุ่งกับข้อมูล คุณสามารถทดสอบเพิ่มเติมได้ถ้ามีคนส่งข้อมูลที่ไม่ถูกต้องให้คุณ:

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: สร้างคลาสแบบเต็มด้วยอินสแตนซ์ จำกัด const เช่นเดียวกับใน Java ส่งต่อประกาศคลาสและจากนั้นกำหนดไว้ในไฟล์ CPP และกำหนดค่าเฉพาะเหมือน enum ฉันทำอะไรแบบนั้นใน C ++ และผลลัพธ์ก็ไม่เป็นที่น่าพอใจตามที่ต้องการเพราะมันจำเป็นต้องใช้รหัสบางอย่างในการจำลอง enum (การสร้างการคัดลอกตัวดำเนินการ = ฯลฯ )

3: ตามที่เสนอไว้ก่อนหน้าใช้ enum ที่ประกาศเป็นการส่วนตัว แม้ว่าที่จริงแล้วผู้ใช้จะเห็นคำจำกัดความเต็มรูปแบบ แต่ผู้ใช้จะไม่สามารถใช้งานได้หรือใช้วิธีการส่วนตัว ดังนั้นโดยปกติคุณจะสามารถแก้ไข enum และเนื้อหาของวิธีการที่มีอยู่โดยไม่จำเป็นต้องคอมไพล์โค้ดซ้ำโดยใช้คลาสของคุณ

ฉันเดาว่าอาจเป็นโซลูชัน 3 หรือ 1


-1

เนื่องจาก enum อาจเป็นขนาดที่รวมกันของขนาดที่แตกต่างกัน (คอมไพเลอร์ตัดสินใจว่าขนาดใดที่ enum ที่กำหนดไว้) ตัวชี้ไปยัง enum จึงสามารถมีขนาดที่แตกต่างกันได้เนื่องจากเป็นประเภทที่สมบูรณ์ (ตัวอักษรมีพอยเตอร์ ตัวอย่าง)

ดังนั้นคอมไพเลอร์ไม่สามารถแม้แต่จะบอกให้คุณส่งต่อค่า enum และผู้ใช้เป็นตัวชี้ไปเพราะแม้แต่ที่นั่นมันต้องการขนาดของ enum


-1

คุณกำหนดการแจงนับเพื่อ จำกัด ค่าที่เป็นไปได้ขององค์ประกอบของประเภทเป็นชุดที่ จำกัด ข้อ จำกัด นี้จะถูกบังคับใช้ในเวลารวบรวม

เมื่อส่งต่อความจริงที่ว่าคุณจะใช้ 'ชุด จำกัด ' ในภายหลังจะไม่เพิ่มค่าใด ๆ : รหัสที่ตามมาจำเป็นต้องทราบค่าที่เป็นไปได้เพื่อที่จะได้รับประโยชน์จากมัน

แม้ว่าคอมไพเลอร์จะกังวลเกี่ยวกับขนาดของชนิดระบุความตั้งใจในการแจงนับจะหายไปเมื่อคุณส่งต่อประกาศ


1
ไม่รหัสที่ตามมาไม่จำเป็นต้องรู้ค่าสำหรับสิ่งนี้ว่าจะมีประโยชน์โดยเฉพาะถ้ารหัสที่ตามมาเป็นเพียงต้นแบบฟังก์ชั่นที่รับหรือส่งคืนพารามิเตอร์ enum ขนาดของประเภทนั้นไม่สำคัญ การใช้การประกาศไปข้างหน้าที่นี่สามารถลบการอ้างอิงการสร้างการเร่งความเร็วการรวบรวม
j_random_hacker

คุณถูก. ความตั้งใจไม่ได้เป็นไปตามค่านิยม แต่เป็นประเภท แก้ไขด้วย 0x Enum ประเภท
xtofl
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.