This discussion is about a name of default value: C#: Should the default value of an enum be None or Unknown?
However, large amount of people I spoken with recently considered default enum values harmful, unnecessary and potentially leading to a bad practice.
As an example consider the following:
enum eJobStates
{
JOB_STATE_INITIALISING,
JOB_STATE_PROCESSING,
JOB_STATE_DONE
};
It would not make sense for a job to be in say, JOB_STATE_UNKNOWN, but you can imagine that any structure that might be used for monitoring of said jobs could use such value.
Are there any best practices / rules of thumb concerning creating a default value when defining an enum? Should they be avoided at all cost whenever possible?
Each enumeration-constant in an enumerator-list names a value of the enumeration set. By default, the first enumeration-constant is associated with the value 0. The next enumeration-constant in the list is associated with the value of ( constant-expression + 1 ), unless you explicitly associate it with another value.
The default value for an enum is zero.
If the first value of the enum variable is not initialized then the C compiler automatically assigns the value 0.
Unless you specify otherwise in the definition of the enumeration, the initial enumerator always has the value zero and the value of each subsequent enumerator is one greater than the previous enumerator. You do not need to specify a starting value of 0, it defaults to 0.
An invalid default value is basically a variant in your design. The object isn't valid when it's created. You should avoid that when it's reasonable. By no means should you avoid it "at all costs."
Some problems require you to start in a variant state. In that case, you have to reason about that invalid value mentally. If you avoid naming it you are actively making your code less expressive. Think about it in terms of communication between you and the person that will have to maintain the code later.
Dealing with it downwind is annoying. You start in a variant state, but by the time it's relevant you hope that it is no longer variant. The strategy I prefer is allow users to ignore the variant state and just throw when I've made a mistake.
namespace FooType {
enum EnumValue {
INVALID = 0
,valid
};
}
struct Foo {
Foo() : val(FooType::INVALID) {}
FooType::EnumValue get() const {
if (val == FooType::INVALID)
throw std::logic_error("variant Foo state");
return val;
}
FooType::EnumValue val;
};
This frees your users from having to reason about your variance, which is worth fighting for.
If you can't get away with that, I usually prefer to degrade to safe and unsafe interfaces.
struct Foo {
Foo() : val(FooType::INVALID) {}
bool get(FooType::EnumValue& val_) const {
if (val == FooType::INVALID)
return false;
val_ = val;
return true;
}
FooType::EnumValue get() const {
FooType::EnumValue val_;
if (!get(val_))
throw std::logic_error("variant Foo state");
return val_;
}
FooType::EnumValue get_or_default(FooType::EnumValue def) const {
FooType::EnumValue val_;
if (!get(val_))
return def;
return val_;
}
FooType::EnumValue val;
};
These sorts of interfaces are good for things like databases, where null values may be expected.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With