Чи може клас перерахунку C ++ мати методи?


145

У мене клас enum з двома значеннями, і я хочу створити метод, який отримує значення і повертає інше. Я також хочу підтримувати безпеку типу (тому я використовую enum class замість enums).

http://www.cplusplus.com/doc/tutorial/other_data_types/ нічого не згадує про методи. Однак у мене склалося враження, що будь-який тип класу може мати методи.


4
Ні, не може. Дивіться тут .
juanchopanza

@octavian Зауважте мою відповідь і подумайте про ваші випадки використання!
πάντα ῥεῖ

@ πάνταῥεῖ Ви абсолютно праві, я читав переслідування, але думав про союз, вбив коментар.
Євген Константин Дінка

@octavian чи ви навіть НЕ запитавши для конкретного випадку використання на всіх, або ви просто хочете мати обмеження стандартів на C ++ 11 enum class/struct Підтверджено?
πάντα ῥεῖ

Я мав на увазі використання… і це було основним питанням
октавіан

Відповіді:


118

Ні, вони не можуть.

Я можу зрозуміти, що enum classчастина сильно набраних перерахунків у C ++ 11, можливо, може означати, що і у вас enumє classриси, але це не так. Моя здобута здогадка полягає в тому, що вибір ключових слів був натхненним шаблоном, який ми використовували до C ++ 11 для отримання перелічених перерахунків:

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

Однак це просто синтаксис. Знову ж таки, enum classне є class.


88
На ## C ++ мені сказали, що "c ++ має на меті бути максимально заплутаним та прихильним до експертів" . Очевидно, що це жарт, але ви
розумієте

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

...

Але, звичайно, це не працює, тому що у переліків не може бути методів (і "це" нічого не означає у вищезгаданому контексті)

Однак якщо ви використовуєте ідею звичайного класу, що містить переклади некласового класу та змінну одного члена, що містить значення цього типу, ви можете отримати надзвичайно близький до потрібного вам синтаксис / поведінку / тип безпеки. тобто:

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 (), operator ==,! = Позначатися як 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 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;

Очевидно, що думати про інкапсуляцію необхідних операцій для повторної / встановлення одиночної / групи бітів, наприклад, значення бітової маски або навіть операції, керовані бітом, було б корисно для маніпулювання таким набором "прапорів".

The struct/ class специфікація просто підтримує кращі показники значень перерахувань для доступу. Ні більше, ні менше!

Способами виходу із обмеження ви не можете оголосити методи для enum (класів) - або використовувати std::bitset(клас обгортки), або бітфілдunion .

unions, і такі об'єднання бітфілдів можуть мати методи (див. тут обмеження!).

У мене є зразок, як перетворити значення бітових масок (як показано вище) у відповідні бітові індекси, які можна використовувати std::bitsetтут: BitIndexConverter.hpp
Я вважав це досить корисним для підвищення читабельності деяких заснованих на "прапорі" рішень алгоритми.


36
Існує більше випадків використання, які вимагають методів для перерахунку enum, наприклад, toString () та fromString (). Кожна (навіть не дуже) сучасна основна мова має це (наприклад, C #, Java, Swift), тільки не C ++.
Майк Лішке

1
Будемо сподіватися на уніфікований синтаксис виклику наступного разу навколо ... open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4165.pdf
sdgfsdh

4

Існує досить сумісна здатність (§) перетворювати перерахунок на клас без необхідності переписувати код, а це означає, що ефективно ви можете робити те, що просили, не надто багато редагуючи.

(§) як вказує ElementW у коментарі, залежний код type_traits не працюватиме, тому, наприклад, не можна використовувати авто тощо. і завжди є помилкою підривати C ++

enum structі enum classтехнічні характеристики стосуються сфери масштабування, тому не є частиною цього.

Ваш оригінальний перерахунок, наприклад, "домашня тварина" (це лише приклад!).

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

(1) Ви модифікуєте це, наприклад, до petEnum (щоб приховати його від наявного коду).

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

(2) Ви додаєте під ним нову декларацію класу (названу з оригінальним перерахунком)

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
Це не повністю сумісно: якщо ви використовуєте значення перерахування з будь-яким видом виду типу, де очікується отримати petім'я типу / екземпляр, будь то шаблони 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"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.