Возможно ли перечисление перечислений в С++?
Можно ли перечислять перечисления в С++.
У меня должно быть что-то вроде:
Типы ошибок:
Type1:
Type2:
Type3:
Каждый из них представляет собой целочисленные значения. Предполагается, что они используются в стеке протокола связи. На принимающей стороне приемник должен декодировать тип и причину ошибки от полученных значений. Если перечисления не могут быть использованы, что было бы лучшим способом сделать это?
Ответы
Ответ 1
Я даже не знаю, что означало бы перечисление перечислений. Но
Обычным способом обработки является либо определение диапазонов в
единственное перечисление:
enum Errors
{
type1 = 0x000,
cause1,
cause2,
type2 = 0x100,
cause3,
cause4,
...
causeMask = 0xFF,
typeMask = 0xFF00
};
Или просто определить отдельные перечисления, в отдельных словах и использовать
unsigned
(или unsigned short
, или что когда-либо) и немного
кастинг по разным причинам.
Каким бы ни было решение, я бы инкапсулировал его в
класс, так что клиентский код должен иметь дело только с errorType()
и errorCause()
; errorCause()
может быть даже шаблоном
значение типа ошибки. (Но где-то вам понадобится явное
специализации для каждого значения типа, поскольку компилятор будет
иначе не знаете, как сопоставить значение, чтобы вызвать тип.)
Ответ 2
Я бы не рекомендовал это делать. Предпочитаете использовать явный тип ошибки, содержащий информацию об ошибках (вы можете добавить строки и т.д.). Кроме того, это не очень безопасный тип. См. Также ответ Джеймса.
Но в любом случае вот злая макро версия:
#define DECL_ERROR_TYPE(errorType, value) , errorType = value << 16
#define DECL_ERROR(errorType, cause, value) , errorType##_##cause = (errorType + value)
#define GET_ERROR_TYPE(error) (error & 0xFFFF0000)
enum Error
{
NoError = 0
DECL_ERROR_TYPE(Type1, 1)
DECL_ERROR(Type1, Cause1, 1)
DECL_ERROR(Type1, Cause2, 2)
DECL_ERROR_TYPE(Type2, 2)
DECL_ERROR(Type2, Cause1, 1)
DECL_ERROR_TYPE(Type3, 3)
DECL_ERROR(Type3, Cause1, 1)
DECL_ERROR(Type3, Cause2, 2)
};
Это позволяет использовать его следующим образом:
Error err1 = Type1_Cause1;
if(Type1 == GET_ERROR_TYPE(err1))
return 0; // Works
Ответ 3
Как сказал Джерри, это невозможно сразу. Один из способов решить эту проблему состоит в том, чтобы иметь две перечисления. Один для категории и один для подкатегории.
Однако, как сказал georgesl, вероятно, это может быть опасно сделать это в протоколе. Вы определенно должны явно определять значения перечисления:
struct Error
{
enum Type {
UNKNOWNTYPE = 0,
TYPE1 = 1,
TYPE2 = 2,
TYPE3 = 3
};
enum Subtype {
UNKNOWNSUBTYPE = 0,
// subtype for error type 1
CAUSE1 = 1001,
CAUSE2 = 1002,
CAUSE3 = 1003,
// subtype for error type 2
CAUSE4 = 2001,
CAUSE5 = 2002
};
Type type;
Subtype subtype;
};
int main()
{
Error error;
error.type = Error::TYPE1;
error.subtype = Error::CAUSE1;
}
Обязательно выберите номера для будущих расширений.
Обновление: этот пример действительно работает.
Альтернативное, более типичное решение:
struct ErrorType
{
enum type {
UNKNOWNTYPE = 0,
TYPE1 = 1,
TYPE2 = 2,
TYPE3 = 3
};
};
struct ErrorSubtype
{
enum type {
UNKNOWNSUBTYPE = 0,
// subtype for error type 1
CAUSE1 = 1001,
CAUSE2 = 1002,
CAUSE3 = 1003,
// subtype for error type 2
CAUSE4 = 2001,
CAUSE5 = 2002
};
};
struct Error
{
ErrorType::type type;
ErrorSubtype::type subtype;
};
int main()
{
Error error;
error.type = ErrorType::TYPE1;
error.subtype = ErrorSubtype::CAUSE1;
}
Ответ 4
Я просто не мог стоять, используя перечисления. Поэтому у меня есть другой ответ, в котором используются явные типы.
Он не является полным, но показывает правильное направление и содержит возможное расширение добавления описаний и т.д.
Вот код для объявления:
struct Error
{
public:
struct ErrorType
{
int _code;
ErrorType(int code) : _code(code << 16) {}
};
private:
friend struct Errors;
ErrorType _type;
int _code;
Error(ErrorType type, int causeCode)
: _type(type), _code(causeCode)
{
}
static std::map<int, Error> _errors;
public:
Error() : _type(-1), _code(-1) {}
static Error FromCode(int code) { return _errors[code]; }
bool IsOfType(const ErrorType& type )
{
return _type._code == type._code;
}
operator int()
{
return _code | _type._code;
}
bool operator == (Error const& other) const
{
return _code == other._code && _type._code == other._type._code;
}
bool operator != (Error const& other) const
{
return _code != other._code || _type._code != other._type._code;;
}
};
std::map<int, Error> Error::_errors;
struct Errors
{
#define BEGIN_TYPE(type, code) struct type : Error::ErrorType { type() : ErrorType(code) {} typedef Errors::##type CurrentType;
#define CAUSE(cause, code) struct cause : Error { cause() : Error(CurrentType(),code) { Error::_errors[*this] = *this; } };
#define END_TYPE() };
// first type is coded manually to show what the macros do...
struct Type1 : Error::ErrorType
{
Type1() : ErrorType(1) { }
typedef Errors::Type1 CurrentType;
struct Cause1 : Error
{
Cause1() : Error(CurrentType(),1) { Error::_errors[*this] = *this; }
};
struct Cause2 : Error
{
Cause2() : Error(CurrentType(),2) { Error::_errors[*this] = *this; }
};
};
BEGIN_TYPE(Type2, 2)
CAUSE(Cause1, 1)
CAUSE(Cause2, 2)
END_TYPE()
};
И вот несколько примеров использования:
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Error err = Errors::Type1::Cause1();
Q_ASSERT( err.IsOfType(Errors::Type1()) );
Q_ASSERT( Errors::Type1::Cause1() == Errors::Type1::Cause1() );
Q_ASSERT( Errors::Type1::Cause1() != Errors::Type2::Cause1() );
int code = err;
qDebug() << code;
Q_ASSERT( Error::FromCode(code) == Errors::Type1::Cause1() );
Q_ASSERT( Error::FromCode(code) != Errors::Type2::Cause1() );
Q_ASSERT( Error::FromCode(code).IsOfType(Errors::Type1()) );
return a.exec();
}
Это не идеальное решение, но показывает, как это можно обработать более явным образом. Есть много улучшений, которые можно сделать...