2013-07-13 3 views
20

Есть ли способ указать конструктор по умолчанию для enum class?User Defined C++ 11 enum class Default Constructor

Я использую enum class, чтобы указать набор значений, допустимых для определенного типа данных в библиотеке: в этом случае это идентификационные номера GPIO-контактов малины Pi. Это выглядит примерно так:

enum class PinID : int {N4 = 4, N17 = 17, /* ...etc... */ }

Точка меня делать это вместо того, чтобы просто использовать, скажем, int является обеспечение того, код безопасен: Я могу static_assert (или в противном случае время компиляции обеспечения - фактический используемый метод не важен для меня), такие вещи, как то, что кто-то не произвел орфографическую ошибку (прохождение 5 вместо 4 и т. д.), и я получаю сообщения об ошибках автоматически для несоответствий типов и т. д.

проблема заключается в том, что enum class имеет конструктор по умолчанию, который - для совместимости с C enum s Предполагаю (поскольку они имеют такое же поведение) - инициализирует эквивалент enum class0. В этом случае нет значения 0. Это означает, что пользователь делает заявление/определение типа:

PinID pid = PinID();

получает нумератор, который явно не определено (и даже, кажется, не «существует», когда один смотрит на код), и может привести к ошибкам во время выполнения. Это также означает, что методы, подобные switch по значениям явно определенных счетчиков, невозможны без ошибки с ошибкой/по умолчанию - чего-то, чего я хочу избежать, поскольку он заставляет меня либо throw, либо сделать что-то вроде возврата boost::optional, которые меньше поддающийся статическому анализу.

Я попытался определить конструктор по умолчанию безрезультатно. Я (отчаянно) пытался определить функцию, которая разделяет имя enum class, но это (довольно неудивительно) привело к странным ошибкам компилятора. Я хочу сохранить возможность отбрасывать enum class до int, со всеми перечислениями нумераторов N# в соответствующие #, поэтому просто «определение», скажем, N4 = 0 является неприемлемым; это для простоты и здравомыслия.

Я думаю, мой вопрос в два раза: есть ли способ получить статическую безопасность после использования enum class? Если нет, какие другие возможности вы бы предпочли? То, что я хочу, это то, что:

  1. по умолчанию constructable
  2. можно сделать по умолчанию построить для произвольного действительного значения
  3. обеспечивает «конечное множество заданных» значения, обеспечиваемое enum class эс
  4. является по крайней мере, типа безопасной как enum class
  5. (предпочтительно) не включает в себя время выполнения полиморфизм

Причина, по которой я хочу конструктивную конструкцию по умолчанию, заключается в том, что я планирую использовать boost::lexical_cast, чтобы уменьшить синтаксические служебные данные, связанные с преобразованиями между значениями enum class и фактическими связанными string s, которые я вывожу в операционную систему (в этом случае sysfs); boost::lexical_cast требует конструктивности по умолчанию.

Ошибки в моих рассуждениях приветствуются - я начинаю подозревать, что enum class es - это правильный объект для неправильной работы, в данном случае; разъяснение будет предложено, если его попросят. Спасибо за ваше время.

ответ

12

Тип, определенный с помощью enum class или enum struct, не является классом, а областью перечисления и не может быть установлен конструктор по умолчанию. Стандарт C++ 11 определяет, что ваш оператор PinID pid = PinID(); даст нулевую инициализацию. Где PinID был определен как enum class. Он также позволяет перечислять типы в целом для хранения значений, отличных от констант перечислителя.

Чтобы понять, что PinID() дает нулевой инициализации требует считывания стандартных секций 3.9.9, 8.5.5, 8.5.7 и 8.5.10 вместе:

8.5.10 - An object whose initializer is an empty set of parentheses, i.e.,(), shall be value-initialized

8.5.7 - To value-initialize an object of type T means: ... otherwise, the object is zero-initialized.

8.5.5 - To zero-initialize an object or reference of type T means: — if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T;

3.9.9 - государства, типа перечисления являются частью набора типов, известных как скалярных типов.

Возможное решение:

Чтобы удовлетворить ваши пункты 1 до 5 вы могли бы написать класс по линиям:

class PinID 
{ 
private: 
    PinID(int val) 
    : m_value(val) 
    {} 

    int m_value; 

public: 
    static const PinID N4; 
    static const PinID N17; 
    /* ...etc... */ 

    PinID() 
    : m_value(N4.getValue()) 
    {} 

    PinID(const PinID &id) 
    : m_value(id.getValue()) 
    {} 

    PinID &operator = (const PinID &rhs) 
    { 
     m_value = rhs.getValue(); 
     return *this; 
    } 

    int getValue() const 
    { 
     return m_value; 
    } 

    // Attempts to create from int and throw on failure. 
    static PinID createFromInt(int i); 

    friend std::istream& operator>>(std::istream &is, PinID &v) 
    { 
     int candidateVal(0); 
     is >> candidateVal; 
     v = PinID::createFromInt(candidateVal); 
     return is; 
    } 
}; 

const PinID PinID::N4 = PinID(4); 
/* ...etc... */ 

Это может дать вам то, что вы должны сделать конкретные попытки получить недопустимые значения. Конструктор по умолчанию и оператор потока должны позволить ему работать с lexical_cast.

Кажется, это зависит от того, насколько важны операции с PinID после его создания, стоит ли писать класс или просто обрабатывать недопустимые значения везде, когда это значение используется.

+1

Просто глядя на это, он выглядит в основном хорошим, но будет ли он работать? У вас есть определение класса, которое включает в себя экземпляр себя как члена ... о, но они статичны. Тогда компилятор не сходит с ума. Умный медведь. – FizzixNerd

+0

@FizzixNerd Я не запускал его, хотя компилятор, но он должен работать ... Я думаю, что если вы добавите потоковые операторы, вы сможете использовать его непосредственно с lexical_cast, я полагаю, бросаю исключение, если приходит недопустимое значение. – PeterSW

+0

@PeterSW, можете ли вы указать, какие элементы в стандарте C++ 11 вы ссылаетесь, говоря об нулевой инициализации? –

4

enum class - это строго типизированный enum; это не class. C++ 11 просто повторно использовал существующее ключевое слово class, чтобы избежать появления нового ключевого слова, которое нарушит совместимость с устаревшим кодом на C++.

Что касается вашего вопроса, то нет способа обеспечить во время компиляции, что литье включает в себя подходящего кандидата. Рассмотреть:

int x; 
std::cin >> x; 
auto p = static_cast<PinID>(x); 

Это совершенно законно, и нет никакого способа статически гарантировать, что пользователь консоли сделал правильную вещь.

Вместо этого вам нужно будет проверить во время выполнения, чтобы это значение было действительным. Чтобы обойти это автоматизированным способом, один из моих сотрудников создал генератор enum, который строит эти проверки и другие полезные подпрограммы с учетом файла с перечисляемыми значениями. Вам нужно будет найти решение, которое будет работать для вас.

+0

Спасибо за ответ! Я знаю, что класс enum не является классом, но он по-прежнему имеет конструктор по умолчанию. Что касается статической проверки, я, конечно же, знаю, что я не могу статически проверять преобразования из string-> enum - для этого я использую boost :: optioanls, но я _can_ статически проверяю хотя бы _some_ перечисления, > строка, которая включает constexpr, не так ли? – FizzixNerd

+3

@FizzixNerd "имеет конструктор по умолчанию" - технически, нет. Он имеет определенное поведение для инициализации значения.Инициализация значения объекта типа класса вызывает конструктор по умолчанию; инициализация значения объекта numeric или enum устанавливает его в ноль; инициализация значения объекта типа указателя устанавливает его в значение нулевого указателя. – aschepler

1

Я знаю, что этот вопрос датирована и что у него уже есть принятый ответ, но вот техника, которая может помочь в такой ситуации, как это с некоторыми из новых функций C++

Вы можете объявить переменную этого класса, либо non static, либо static, это можно сделать несколькими способами, разрешенными для поддержки вашего текущего компилятора.


Non Static:

#include <iostream> 
#include <array> 

template<unsigned... IDs> 
class PinIDs { 
private: 
    const std::array<unsigned, sizeof...(IDs)> ids { IDs... }; 
public: 
    PinIDs() = default; 
    const unsigned& operator[](unsigned idx) const { 
     if (idx < 0 || idx > ids.size() - 1) { 
      return -1; 
     } 
     return ids[idx]; 
    } 
}; 

Статическая:- Есть 3 способа написать это: (First One - C++ 11 или 14 или выше) последние 2 (C++ 17).

Не цитируйте меня на части C++ 11; Я не совсем уверен, когда впервые были представлены вариативные шаблоны или пакеты параметров.

template<unsigned... IDs> 
class PinIDs{ 
private:   
    static const std::array<unsigned, sizeof...(IDs)> ids; 
public:  
    PinIDs() = default;  
    const unsigned& operator[](unsigned idx) const { 
     if (idx < 0 || idx > ids.size() - 1) { 
      return -1; 
     } 
     return ids[idx]; 
    } 
}; 

template<unsigned... IDs> 
const std::array<unsigned, sizeof...(IDs)> PinIDs<IDs...>::ids { IDs... }; 

template<unsigned... IDs> 
class PinIDs{ 
private: 
    static constexpr std::array<unsigned, sizeof...(IDs)> ids { IDs... }; 
public: 
    PinIDs() = default;  
    const unsigned& operator[](unsigned idx) const { 
     if (idx < 0 || idx > ids.size() - 1) { 
      return -1; 
     } 
     return ids[idx]; 
    } 
}; 

template<unsigned... IDs> 
class PinIDs{ 
private: 
    static inline const std::array<unsigned, sizeof...(IDs)> ids { IDs... }; 
public:  
    PinIDs() = default;  
    const unsigned& operator[](unsigned idx) const { 
     if (idx < 0 || idx > ids.size() - 1) { 
      return -1; 
     } 
     return ids[idx]; 
    } 
}; 

Все примеры выше либо не-статической или статической работы с такой же случае использования ниже и обеспечивают правильные результаты:

int main() { 
    PinIDs<4, 17, 19> myId; 

    std::cout << myId[0] << " "; 
    std::cout << myId[1] << " "; 
    std::cout << myId[2] << " "; 

    std::cout << "\nPress any key and enter to quit." << std::endl; 
    char c; 
    std::cin >> c; 

    return 0; 
} 

Выход

4 17 19 
Press any key and enter to quit. 

С этим типом шаблона класса, используя VARIADIC список параметров, вы не должны использовать любой конструктор, но по умолчанию. Я добавил проверку границ в массив так, чтобы operator[] не превышал границ его размера; Я мог выбросить ошибку, но с unsigned тип I просто просто вернулся -1 как недопустимое значение.

С этим типом нет значения по умолчанию, поскольку вам необходимо создать экземпляр этого объекта через список параметров шаблона с помощью одного или нескольких значений. Если кто-то хочет, они могут specialize this class с единственным параметром 0 для типа по умолчанию. Когда вы создаете экземпляр этого типа объекта; он окончательный, поскольку он не может быть изменен из его декларации. Это объект const и по-прежнему считается конструктивным по умолчанию.