C ++ enums ลงนามหรือไม่ได้ลงนาม?


107

C ++ enums ลงนามหรือไม่ได้ลงนาม? และโดยส่วนขยายจะปลอดภัยหรือไม่ที่จะตรวจสอบอินพุตโดยตรวจสอบว่าเป็น <= ค่าสูงสุดของคุณและเว้นไว้> = ค่าขั้นต่ำของคุณ (สมมติว่าคุณเริ่มต้นที่ 0 และเพิ่มขึ้นทีละ 1)


เมื่อเราใช้ประเภท enum ในบริบทที่ต้องใช้สัญลักษณ์ของมันเรากำลังพูดถึงการแปลง enum เป็นประเภทอินทิกรัลโดยปริยาย มาตรฐาน C ++ 03 กล่าวว่าสิ่งนี้ทำโดย Integral Promotion ไม่มีอะไรเกี่ยวข้องกับประเภทพื้นฐานของ enum ดังนั้นฉันไม่เข้าใจว่าทำไมทุกคำตอบในที่นี้จึงกล่าวถึงประเภทพื้นฐานไม่ได้กำหนดโดยมาตรฐาน? ฉันอธิบายพฤติกรรมที่คาดหวังไว้ที่นี่: stackoverflow.com/questions/24802322/…
JavaMan

คำตอบ:


60

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

ในระยะสั้น: คุณไม่สามารถพึ่งพา enum ที่ลงชื่อหรือไม่ได้ลงนาม


28
คำตอบของ Michael Burr (ซึ่งอ้างถึงมาตรฐาน) บอกเป็นนัยว่าจริง ๆ แล้วคุณสามารถพึ่งพาได้จากการลงนามหากคุณกำหนดค่า enum เป็นค่าลบเนื่องจากประเภทสามารถ "แสดงค่าตัวนับทั้งหมดที่กำหนดไว้ในการแจงนับ"
Samuel Harmer

101

ไปที่มากันเถอะ นี่คือสิ่งที่เอกสารมาตรฐาน C ++ 03 (ISO / IEC 14882: 2003) ระบุไว้ใน 7.2-5 (การประกาศการแจงนับ):

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

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


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

@jww - ขึ้นอยู่กับว่าคุณใช้คอมไพเลอร์อะไรอยู่แน่ ๆ เนื่องจากมาตรฐานไม่ได้กำหนดประเภทพื้นฐานและปล่อยให้สิ่งนี้ไปสู่การใช้งานคุณจึงต้องดูเอกสารประกอบเครื่องมือของคุณและดูว่าตัวเลือกนี้เป็นไปได้หรือไม่ หากคุณต้องการรับประกันพฤติกรรมบางอย่างในโค้ดของคุณทำไมไม่ใช้สมาชิก enum ที่คุณใช้ในนิพจน์
ysap

22

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

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum

11
เฉพาะในอนาคต C ++ 0x มาตรฐาน
dalle

3
@dalle Microsoft compilator ยังอนุญาตให้พิมพ์ enums msdn.microsoft.com/en-us/library/2dzy4k6e(v=vs.80).aspx
teodozjan

15

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

ใน C ++ 0x การแจงนับที่พิมพ์อย่างรุนแรงจะถูกเพิ่มซึ่งจะช่วยให้คุณสามารถระบุประเภทของ enum เช่น:

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum

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

enum Fruit { Apple, Banana };

enum Fruit fruitVariable = Banana;  // Okay, Banana is a member of the Fruit enum
fruitVariable = 1;  // Error, 1 is not a member of enum Fruit
                    // even though it has the same value as banana.

ฉันคิดว่าตัวอย่างที่สองของคุณสับสนเล็กน้อย :)
Miral

5

คอมไพเลอร์สามารถตัดสินใจได้ว่าจะเซ็นชื่อหรือไม่ลงนาม enums

อีกวิธีหนึ่งในการตรวจสอบ enums คือการใช้ enum เป็นตัวแปรชนิด ตัวอย่างเช่น:

enum Fruit
{
    Apple = 0,
    Banana,
    Pineapple,
    Orange,
    Kumquat
};

enum Fruit fruitVariable = Banana;  // Okay, Banana is a member of the Fruit enum
fruitVariable = 1;  // Error, 1 is not a member of enum Fruit even though it has the same value as banana.

5

แม้แต่คำตอบเก่า ๆ บางคำก็ได้รับการโหวตถึง 44 ครั้งฉันก็มักจะไม่เห็นด้วยกับคำตอบทั้งหมด ในระยะสั้นฉันไม่คิดว่าเราควรสนใจเกี่ยวกับunderlying typeenum

ก่อนอื่นประเภท C ++ 03 Enum เป็นประเภทที่แตกต่างกันโดยไม่มีแนวคิดในการลงนาม ตั้งแต่มาตรฐาน C ++ 03dcl.enum

7.2 Enumeration declarations 
5 Each enumeration defines a type that is different from all other types....

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

9 The value of an enumerator or an object of an enumeration type is converted to an integer by integral promotion (4.5).

และเห็นได้ชัดว่าประเภทพื้นฐานของ enum ไม่มีส่วนเกี่ยวข้องกับ Integral Promotion เนื่องจากมาตรฐานกำหนด Integral Promotion ไว้ดังนี้:

4.5 Integral promotions conv.prom
.. An rvalue of an enumeration type (7.2) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration
(i.e. the values in the range bmin to bmax as described in 7.2: int, unsigned int, long, or unsigned long.

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

ดูคำถามที่เกี่ยวข้องของฉัน เครื่องหมายของ C ++ Enum Type ไม่ถูกต้องหลังจากแปลงเป็น Integral Type


-Wsign-conversionมันเป็นเรื่องสำคัญเมื่อคุณกำลังรวบรวมกับ เราใช้เพื่อช่วยจับข้อผิดพลาดที่ไม่ได้ตั้งใจในโค้ดของเรา แต่+1สำหรับการอ้างถึงมาตรฐานและชี้ให้เห็นว่า enum ไม่มีประเภท ( signedเทียบกับunsigned) ที่เกี่ยวข้อง
jww

4

ในอนาคตด้วย C ++ 0x การแจงนับที่พิมพ์มากจะพร้อมใช้งานและมีข้อดีหลายประการ (เช่นประเภทความปลอดภัยประเภทพื้นฐานที่ชัดเจนหรือขอบเขตที่ชัดเจน) ด้วยเหตุนี้คุณจึงสามารถมั่นใจได้ดีขึ้นถึงสัญลักษณ์ของประเภท


4

นอกเหนือจากสิ่งที่คนอื่นพูดเกี่ยวกับการลงนาม / ไม่ได้ลงนามแล้วนี่คือสิ่งที่มาตรฐานกล่าวเกี่ยวกับช่วงของประเภทที่แจกแจง:

7.2 (6): "สำหรับการแจงนับโดยที่ e (นาที) เป็นตัวแจงนับที่เล็กที่สุดและ e (สูงสุด) มีค่ามากที่สุดค่าของการแจงนับคือค่าของประเภทที่อยู่ในช่วง b (นาที) ถึง b (สูงสุด ) โดยที่ b (min) และ b (max) เป็นค่าที่เล็กที่สุดและมากที่สุดของ bitfield ที่เล็กที่สุดที่สามารถจัดเก็บ e (min) และ e (max) ได้เป็นไปได้ที่จะกำหนดการแจงนับที่ไม่ได้กำหนดค่าไว้ โดยผู้แจงนับ "

ตัวอย่างเช่น:

enum { A = 1, B = 4};

กำหนดประเภทการแจกแจงโดยที่ e (min) คือ 1 และ e (max) คือ 4 หากประเภทที่อยู่ภายใต้ลงนาม int แล้ว bitfield ที่เล็กที่สุดที่ต้องการจะมี 4 บิตและหาก ints ในการนำไปใช้ของคุณเป็นส่วนเสริมสองค่าช่วงที่ถูกต้องของ enum คือ -8 ถึง 7 หากไม่ได้ลงนามประเภทพื้นฐานจะมี 3 บิตและช่วงคือ 0 ถึง 7 ตรวจสอบเอกสารคอมไพเลอร์ของคุณว่าคุณสนใจ (ตัวอย่างเช่นหากคุณต้องการส่งค่าอินทิกรัลอื่นที่ไม่ใช่ตัวนับไปยัง ประเภทที่แจกแจงแล้วคุณต้องรู้ว่าค่าอยู่ในช่วงของการแจงนับหรือไม่ - ถ้าไม่ใช่ค่า enum ที่เป็นผลลัพธ์จะไม่ระบุ)

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


0

ตรวจสอบด้วยstd::is_signed<std::underlying_type+ ขอบเขต enums เริ่มต้นเป็นint

https://en.cppreference.com/w/cpp/language/enumหมายถึง:

main.cpp

#include <cassert>
#include <iostream>
#include <type_traits>

enum Unscoped {};
enum class ScopedDefault {};
enum class ScopedExplicit : long {};

int main() {
    // Implementation defined, let's find out.
    std::cout << std::is_signed<std::underlying_type<Unscoped>>() << std::endl;

    // Guaranteed. Scoped defaults to int.
    assert((std::is_same<std::underlying_type<ScopedDefault>::type, int>()));

    // Guaranteed. We set it ourselves.
    assert((std::is_same<std::underlying_type<ScopedExplicit>::type, long>()));
}

GitHub อัปสตรี

รวบรวมและเรียกใช้:

g++ -std=c++17 -Wall -Wextra -pedantic-errors -o main main.cpp
./main

เอาท์พุต:

0

ทดสอบบน Ubuntu 16.04, GCC 6.4.0

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