คลาส C ++ enum สามารถมีเมธอดได้หรือไม่?


145

ฉันมีคลาส enum ที่มีค่าสองค่าและฉันต้องการสร้างวิธีที่รับค่าและส่งกลับค่าอื่น ฉันต้องการรักษาความปลอดภัยประเภท (นั่นคือเหตุผลที่ฉันใช้คลาส enum แทน enums)

http://www.cplusplus.com/doc/tutorial/other_data_types/ไม่ได้พูดถึงวิธีการใด ๆ อย่างไรก็ตามฉันรู้สึกว่าคลาสทุกประเภทสามารถมีวิธีการได้


4
ไม่มันไม่สามารถ ดูที่นี่
juanchopanza

@octavian สังเกตคำตอบของฉันและคิดใหม่เกี่ยวกับกรณีการใช้งานของคุณโปรด!
πάνταῥεῖ

@ πάνταῥεῖคุณถูกต้องทั้งหมดฉันได้อ่าน enum แต่คิดว่าสหภาพแรงงานฆ่าความคิดเห็น
Eugen Constantin Dinca

@octavian ที่คุณจะขอให้กรณีที่ใช้โดยเฉพาะอย่างยิ่งที่ทั้งหมดหรือไม่คุณก็ต้องการที่จะมีข้อ จำกัด ในมาตรฐานC ++ 11 enum class/structยืนยัน?
πάνταῥεῖ

ฉันเคยใช้ในใจ ... และนี่คือปัญหาพื้นฐาน
Octavian

คำตอบ:


118

ไม่พวกเขาทำไม่ได้

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

class Foo {
public:
  enum {BAR, BAZ};
};

อย่างไรก็ตามนั่นเป็นเพียงไวยากรณ์ อีกครั้งไม่ได้เป็นenum classclass


88
เมื่อวันที่ ## c ++ ผมก็บอกว่า"C ++ มีจุดมุ่งหมายที่จะเป็นความสับสนและผู้เชี่ยวชาญด้านการเป็นมิตรที่เป็นไปได้" เห็นได้ชัดว่ามันเป็นเรื่องตลก แต่คุณได้รับความคิด :)
สเตฟาโน Sanfilippo

4
unionไม่ใช่สิ่งที่จอห์นโดจะพิจารณาชั้นเรียนด้วย แต่พวกเขาสามารถมีฟังก์ชั่นสมาชิก และคลาสไม่ได้บังคับให้ทำหน้าที่สมาชิก การใช้ตัวออกแบบเช่นvalueหรือthisสิ่งที่คล้ายกันenum Size { Huge, Mega, Apocalypse; bool operator<(X rhs) const { return *this < rhs; }(ที่นี่ยังอนุญาตให้;) มันสามารถทำให้รู้สึกเหมือนกับฟังก์ชั่นรูปแบบอื่น ๆ
เซบาสเตียนมัค

85

ในขณะที่คำตอบที่ว่า "คุณไม่สามารถ" ถูกต้องในทางเทคนิคฉันเชื่อว่าคุณอาจสามารถทำสิ่งที่คุณต้องการโดยใช้แนวคิดต่อไปนี้:

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

Fruit f = Fruit::Strawberry;
f.IsYellow();

และคุณหวังว่ารหัสจะเป็นดังนี้:

enum class Fruit : uint8_t
{
  Apple, 
  Pear,
  Banana,
  Strawberry,

  bool IsYellow() { return this == Banana; }
};

...

แต่แน่นอนมันไม่ได้ผลเพราะ enums ไม่มีวิธี (และ 'สิ่งนี้' ไม่ได้มีความหมายอะไรในบริบทข้างต้น)

อย่างไรก็ตามหากคุณใช้แนวคิดของคลาสปกติที่มี enum ที่ไม่ใช่คลาสและตัวแปรสมาชิกเดี่ยวที่มีค่าประเภทนั้นคุณสามารถเข้าใกล้ความปลอดภัยของไวยากรณ์ / พฤติกรรม / ประเภทที่คุณต้องการ เช่น:

class Fruit
{
public:
  enum Value : uint8_t
  {
    Apple,
    Pear,
    Banana,
    Strawberry
  };

  Fruit() = default;
  constexpr Fruit(Value aFruit) : value(aFruit) { }

#if Enable switch(fruit) use case:
  operator Value() const { return value; }  // Allow switch and comparisons.
                                            // note: Putting constexpr here causes
                                            // clang to stop warning on incomplete
                                            // case handling.
  explicit operator bool() = delete;        // Prevent usage: if(fruit)
#else
  constexpr bool operator==(Fruit a) const { return value == a.value; }
  constexpr bool operator!=(Fruit a) const { return value != a.value; }
#endif

  constexpr bool IsYellow() const { return value == Banana; }

private:
  Value value;
};

ตอนนี้คุณสามารถเขียน:

Fruit f = Fruit::Strawberry;
f.IsYellow();

และคอมไพเลอร์จะป้องกันสิ่งต่าง ๆ เช่น:

Fruit f = 1;  // Compile time error.

คุณสามารถเพิ่มวิธีการต่าง ๆ เช่น:

Fruit f("Apple");

และ

f.ToString();

สามารถรองรับ


1
ไม่ควรเป็น IsYellow (), โอเปอเรเตอร์ ==,! = ทำเครื่องหมายเป็น constexpr?
Jarek C

ฉันได้รับ "ข้อผิดพลาด: ไม่มีตัวดำเนินการไบนารีก่อนโทเค็น" สลับ ""
Pedro77

18

การให้ความสำคัญกับคำอธิบายของคำถามแทนที่จะเป็นชื่อคำตอบที่เป็นไปได้คือ

struct LowLevelMouseEvent {
    enum Enum {
        mouse_event_uninitialized = -2000000000, // generate crash if try to use it uninitialized.
        mouse_event_unknown = 0,
        mouse_event_unimplemented,
        mouse_event_unnecessary,
        mouse_event_move,
        mouse_event_left_down,
        mouse_event_left_up,
        mouse_event_right_down,
        mouse_event_right_up,
        mouse_event_middle_down,
        mouse_event_middle_up,
        mouse_event_wheel
    };
    static const char* ToStr (const type::LowLevelMouseEvent::Enum& event)
    {
        switch (event) {
            case mouse_event_unknown:         return "unknown";
            case mouse_event_unimplemented:   return "unimplemented";
            case mouse_event_unnecessary:     return "unnecessary";
            case mouse_event_move:            return "move";
            case mouse_event_left_down:       return "left down";
            case mouse_event_left_up:         return "left up";
            case mouse_event_right_down:      return "right down";
            case mouse_event_right_up:        return "right up";
            case mouse_event_middle_down:     return "middle down";
            case mouse_event_middle_up:       return "middle up";
            case mouse_event_wheel:           return "wheel";
            default:
                Assert (false);
                break;
        }
        return "";
    }
};

4

ตามที่ระบุไว้ในคำตอบอื่น ๆไม่มี แม้enum classจะไม่ได้เรียน


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

enum class Flags : unsigned char {
    Flag1 = 0x01 , // Bit #0
    Flag2 = 0x02 , // Bit #1
    Flag3 = 0x04 , // Bit #3
    // aso ...
}

// Sets both lower bits
unsigned char flags = (unsigned char)(Flags::Flag1 | Flags::Flag2);

// Set Flag3
flags |= Flags::Flag3;

// Reset Flag2
flags &= ~Flags::Flag2;

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

struct/ class ข้อมูลจำเพาะรองรับการกำหนดขอบเขตค่า enum ที่ดีขึ้นสำหรับการเข้าถึง ไม่มากไม่น้อย!

วิธีการที่จะได้รับจากข้อ จำกัด ที่คุณไม่สามารถประกาศวิธีการ enum (เรียน)เป็นอย่างใดอย่างหนึ่งที่จะใช้std::bitset(ชั้นเสื้อคลุม) หรือbitfieldunion

unions และสหภาพบิตฟิลด์ดังกล่าวสามารถมีวิธี (ดูที่นี่สำหรับข้อ จำกัด !)

ฉันมีตัวอย่างวิธีแปลงค่าบิตมาสก์ (ดังที่แสดงด้านบน) เป็นดัชนีบิตที่สอดคล้องกันซึ่งสามารถใช้งานได้std::bitsetที่นี่: BitIndexConverter.hpp
ฉันพบว่ามันมีประโยชน์มากสำหรับการเพิ่มความสามารถในการอ่านของการถอดรหัส 'flag' อัลกอริทึม


36
มีกรณีการใช้งานเพิ่มเติมที่รับประกันวิธีการของ enum classe เช่น toString () และ fromString () ภาษาหลักที่ทันสมัยทุกภาษา (แม้จะไม่เป็นเช่นนั้น) มี (เช่น C #, Java, Swift) ไม่ใช่ C ++
Mike Lischke

1
หวังว่าจะได้รับการโทรแบบรวมศูนย์ในครั้งต่อไป ... open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4165.pdf
sdgfsdh

4

มีความสามารถในการใช้งานร่วมกันได้สวย (fact) ในการ refactor เป็น enum ในชั้นเรียนโดยไม่ต้องเขียนรหัสใหม่ซึ่งหมายความว่าคุณสามารถทำสิ่งที่คุณขอให้ทำได้โดยไม่ต้องแก้ไขมากเกินไป

(§) ตามที่ ElementW ชี้ให้เห็นในความคิดเห็นรหัสtype_traitsไม่ทำงานดังนั้นเช่นเราไม่สามารถใช้งานอัตโนมัติ ฯลฯ อาจมีวิธีจัดการกับสิ่งเหล่านี้ แต่ท้ายที่สุดแล้วการแปลง enum เป็นคลาส และเป็นความผิดพลาดเสมอในการทำลาย C ++

enum structและenum classรายละเอียดจะเกี่ยวกับการกำหนดขอบเขตเพื่อให้ได้เป็นส่วนหนึ่งของเรื่องนี้

Enum ดั้งเดิมของคุณคือเช่น 'สัตว์เลี้ยง' (นี่เป็นตัวอย่างเท่านั้น!)

enum pet { 
    fish, cat, dog, bird, rabbit, other 
};

(1) คุณปรับเปลี่ยนสิ่งนั้นเป็นเช่น petEnum (เพื่อซ่อนจากรหัสที่มีอยู่ของคุณ)

enum petEnum { 
    fish, cat, dog, bird, rabbit, other 
};

(2) คุณเพิ่มการประกาศคลาสใหม่ด้านล่าง (ตั้งชื่อด้วย enum ดั้งเดิม)

class pet {
    private:
        petEnum value;
        pet() {}

    public:
        pet(const petEnum& v) : value{v} {} //not explicit here.
        operator petEnum() const { return value; }
        pet& operator=(petEnum v) { value = v; return *this;}
        bool operator==(const petEnum v) const { return value == v; }
        bool operator!=(const petEnum v) const { return value != v; }
 //     operator std::string() const;

};

(3) ตอนนี้คุณสามารถเพิ่มวิธีการเรียนใด ๆ ที่คุณต้องการให้กับชั้นเรียนสัตว์เลี้ยงของคุณ เช่น. ผู้ประกอบการสตริง

    pet::operator std::string() const {
        switch (value) {
            case fish: return "fish";
            case cat:  return "cat";
            case dog:  return "dog";
            case bird: return "bird";
            case rabbit: return "rabbit";
            case other: return "Wow. How exotic of you!";
        }
    }

ตอนนี้คุณสามารถใช้เช่น std :: cout ...

int main() {
    pet myPet = rabbit;
    if(myPet != fish) {
        cout << "No splashing! ";
    }
    std::cout << "I have a " << std::string(myPet) << std::endl;
    return 0;
}

1
มันเป็นความไม่เข้ากันได้อย่างเต็มที่: ถ้าคุณใช้ค่า enum กับชนิดประเภทหักใด ๆ ที่คาดว่าจะได้รับpettypename / อินสแตนซ์ไม่ว่าจะเป็นแม่แบบautoหรือdecltypeแบ่งนี้ที่คุณได้รับpetEnumแทน
ElementW

0

อาจไม่ตอบสนองทุกความต้องการของคุณ แต่ด้วยผู้ให้บริการที่ไม่ใช่สมาชิกคุณยังคงสนุกไปกับมัน ตัวอย่างเช่น:

#include <iostream>

enum class security_level
{
    none, low, medium, high
};

static bool operator!(security_level s) { return s == security_level::none; }

static security_level& operator++(security_level& s)
{
    switch(s)
    {
        case security_level::none: s = security_level::low; break;
        case security_level::low: s = security_level::medium; break;
        case security_level::medium: s = security_level::high; break;
        case security_level::high: break;
    }
    return s;
}

static std::ostream & operator<<(std::ostream &o, security_level s)
{
    switch(s)
    {
        case security_level::none: return o << "none";
        case security_level::low: return o << "low";
        case security_level::medium: return o << "medium";
        case security_level::high: return o << "high";
    }
}

รหัสนี้อนุญาตเช่น

security_level l = security_level::none;   
if(!!l) { std::cout << "has a security level: " << l << std::endl; } // not reached
++++l;
if(!!l) { std::cout << "has a security level: " << l << std::endl; } // reached: "medium"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.