Может ли класс перечисления C ++ иметь методы?

145

У меня есть класс enum с двумя значениями, и я хочу создать метод, который получает значение и возвращает другое. Я также хочу поддерживать безопасность типов (поэтому я использую класс enum вместо enum).

http://www.cplusplus.com/doc/tutorial/other_data_types/ ничего не упоминает о методах. Однако у меня сложилось впечатление, что класс любого типа может иметь методы.

Октавиан
источник
4
Нет, не может. Смотрите здесь .
juanchopanza
@octavian Запомните мой ответ и переосмыслите ваши варианты использования, пожалуйста!
πάντα ῥεῖ
@ πάνταῥεῖ Вы совершенно правы, я прочитал enum, но подумал, объединение, убил комментарий.
Евгений Константин Динка
@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();

может быть поддержано.

jtlim
источник
1
Не должно быть также IsYellow (), оператор ==,! = Помечены как constexpr?
Jarek C
Я получаю сообщение об ошибке: отсутствует двоичный оператор перед токеном "switch" "
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;

Очевидно, что для манипулирования таким набором «флагов» было бы полезно инкапсулировать необходимые операции для переустановки / установки одного / группы битов, например, с помощью значения битовой маски или даже операций с битовым индексом.

struct/ class спецификация просто поддерживает лучшую область видимости значений enum для доступа. Ни больше ни меньше!

Способы выхода из ограничения, которые вы не можете объявить методами для перечислений (классов), это либо использовать std::bitset(класс-обертку), либо битовое полеunion .

unions, и такие битовые союзы могут иметь методы (см здесь для ограничений!).

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

πάντα ῥεῖ
источник
36
Существуют и другие варианты использования, которые требуют методов для enum classe, например toString () и fromString (). Каждый (даже не очень) современный основной язык имеет это (например, C #, Java, Swift), но не C ++.
Майк Лишке
1
Будем надеяться на единый синтаксис вызовов в следующий раз ... open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4165.pdf
sdgfsdh
4

Существует довольно совместимая возможность (§) реорганизовать перечисление в класс без необходимости переписывать ваш код, что означает, что вы можете эффективно выполнять то, что просили, без слишком большого редактирования.

(§), как указывает ElementW в комментарии, зависимый от type_traits код работать не будет, поэтому, например, нельзя использовать auto и т. Д. Может быть какой-то способ обработки таких вещей, но в конце можно преобразовать перечисление в класс, и это всегда ошибка подорвать 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"
Johannes
источник