Пользовательский перечислимый класс C ++ 11 конструктор по умолчанию

Есть ли способ указать конструктор по умолчанию enum class?

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

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

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

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

PinID pid = PinID();

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

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

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

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

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

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

25

Решение

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

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

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

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

8.5.5To 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 после его создания, стоит ли писать класс или просто обрабатывать недопустимые значения везде, где оно используется.

13

Другие решения

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

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

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

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

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

4

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

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

Статическая: — Есть 3 способа написать это: (Первый — 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.

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

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

1
По вопросам рекламы [email protected]