Подтвердить что ты не робот

Пользовательский С++ 11 enum class Default Constructor

Можно ли указать конструктор по умолчанию для enum class?

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

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

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

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

PinID pid = PinID();

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

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

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

  • по умолчанию конструктивен
  • можно сделать конструкцией по умолчанию для произвольного допустимого значения
  • предоставляет "конечный набор заданных" значений, предоставляемых enum class es
  • по крайней мере безопасен как тип enum class
  • (предпочтительно) не включает полиморфизм времени выполнения.

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

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

4b9b3361

Ответ 1

Тип, определенный с помощью enum class или enum struct, не является классом a, а областью перечисления и не может быть установлен конструктор по умолчанию. Стандарт С++ 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 после создания, стоит ли писать класс или просто обрабатывать недопустимые значения везде, когда используется значение.

Ответ 2

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

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

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

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

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

Ответ 3

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

Вы можете объявить эту переменную класса либо non static, либо 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];
    }
};

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

Не цитируйте меня на части С++ 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.

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

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