Какова цель анонимных объявлений enum
, таких как:
enum { color = 1 };
Почему бы просто не объявить int color = 1
?
Какова цель анонимных объявлений enum
, таких как:
enum { color = 1 };
Почему бы просто не объявить int color = 1
?
Перечисления не занимают места и неизменяемы.
Если вы использовали const int color = 1;
, тогда вы решили бы проблему с изменчивостью, но если бы кто-то взял адрес color
(const int* p = &color;
), тогда для этого нужно было бы выделить пространство для него. Это не может быть большой проблемой, но если вы явно не хотите, чтобы люди могли принимать адрес color
, вы могли бы также предотвратить его.
Также при объявлении константного поля в классе он должен быть static const
(недействителен для современного С++) и не все компиляторы поддерживают встроенную инициализацию статических константных членов.
Отказ от ответственности: Этот ответ не следует принимать за рекомендации по использованию enum
для всех числовых констант. Вы должны делать то, что вы (или ваши корова-орки) считаете более читаемыми. Ответ просто перечисляет некоторые причины, по которым можно было бы использовать enum
.
Это так называемый enum trick для объявляющий константу целочисленного времени компиляции. Преимущество заключается в том, что он гарантирует, что никакая переменная не создается и, следовательно, нет накладных расходов во время выполнения. Большинство компиляторов вообще не вносят никаких накладных расходов с целыми константами.
Если это старый код, то для "enum hack" может быть использовано перечисление.
Вы можете узнать больше о "enum hack", например, по этой ссылке: enum hack
Одно использование этого - это когда вы делаете метапрограммирование шаблона, потому что объекты перечисления не lvalues, а static const
- члены. Это также было распространенным обходным решением для компиляторов, которое не позволяло вам инициализировать статические интегральные константы в определении класса. Это объясняется в другом вопросе.
(1) int color = 1;
color
является изменяемым (случайно).
(2) enum { color = 1 };
color
не может быть изменен.
Другим вариантом для enum
является
const int color = 1; // 'color' is unmutable
Оба enum
и const int
предлагают точно такую же концепцию; это вопрос выбора. Что касается распространенного мнения, что enum
сэкономить место, IMO не связано с этим ограничением памяти, компилятор достаточно умен, чтобы оптимизировать const int
при необходимости.
[Примечание: если кто-то пытается использовать const_cast<>
на const int
; это приведет к поведению undefined (что плохо). Однако для enum
это невозможно. Поэтому мой личный фаворит enum
]
Когда вы используете
enum {color = 1}
вы не используете память, как будто
#define color 1
Если вы объявите переменную
int color=1
Затем вы берете память за значение, которое нельзя изменить.
Я не вижу в нем упоминания, другое использование - охват ваших констант. В настоящее время я работаю над кодом, написанным с использованием Visual Studio 2005, и теперь он портирован на android-g++. В VS2005 у вас может быть такой код enum MyOpts { OPT1 = 1 };
и использовать его как MyOpts:: OPT1, и компилятор не жаловался на него, хотя он и недействителен. g++ сообщает такой код как ошибку, поэтому одним из решений является использование анонимного enum следующим образом: struct MyOpts { enum {OPT1 =1}; };
, и теперь оба компилятора счастливы.
Читаемость и производительность.
Details are describbed as notes to examples below.
В Unreal Engine 4 (игровой движок C++) у меня есть следующее свойство (переменная-член, доступная для движка):
/// Floor Slope.
UPROPERTY
(
Category = "Movement",
VisibleInstanceOnly,
BlueprintGetter = "BP_GetFloorSlope",
BlueprintReadOnly,
meta =
(
ConsoleVariable = "Movement.FloorSlope",
DisplayName = "Floor Slope",
ExposeOnSpawn = true,
NoAutoLoad
)
)
float FloorSlope = -1.f;
Это значение наклона, на котором игрок стоит (значение ∈ [0; 90) °), если таковое имеется.
Из-за ограничений двигателя он не может быть ни std::optional
, ни TOptional
.
Я нашел решение добавить еще одну самоопределяемую переменную bIsOnFloor
.
bool bIsOnFloor = false;
Мой внутренний установщик C++ только для FloorSlope
имеет следующий вид:
void UMovement::SetFloorSlope(const float& FloorSlope) noexcept
contract [[expects audit: FloorSlope >= 0._deg && FloorSlope < 90._deg]]
{
this->bIsOnFloor = true;
this->FloorSlope = FloorSlope;
AUI::UI->Debug->FloorSlope = FString::Printf(L"Floor Slope: %2.0f", FloorSlope);
};
Добавление особого случая, когда параметр FloorSlope
будет принимать аргумент -1.f
, будет трудно угадать и не будет удобным для пользователя.
Вместо этого я бы лучше создал поле False
enum
:
enum { False };
Таким образом, я могу просто перегрузить функцию SetFloorSlope
, которая использует False
вместо -1.f
.
void UMovement::SetFloorSlope([[maybe_unused]] const decltype(False)&) noexcept
{
this->bIsOnFloor = false;
this->FloorSlope = -1.f;
AUI::UI->Debug->FloorSlope = L"Floor Slope: —";
};
Когда персонаж игрока попадает на пол после применения силы тяжести к нему в тик, я просто вызываю:
SetFloorSlope(FloorSlope);
… где FloorSlope
- это значение float
∈ [0; 90) °.
В противном случае (если он не попадает в пол), я звоню:
SetFloorSlope(False);
Эта форма (в отличие от прохождения -1.f
) гораздо более читабельна и не требует пояснений.
Другим примером может быть предотвращение или принудительная инициализация.
Упомянутый выше Unreal Engine 4 обычно использует FHitResult
struct
, содержащую информацию об одном попадании следа, например, о точке удара и нормали поверхности в этой точке.
Этот сложный struct
вызывает метод Init
по умолчанию, устанавливая некоторые значения для определенных переменных-членов. Это может быть принудительно или запрещено (общедоступные документы: FHitResult
#constructor):
FHitResult()
{
Init();
}
explicit FHitResult(float InTime)
{
Init();
Time = InTime;
}
explicit FHitResult(EForceInit InInit)
{
Init();
}
explicit FHitResult(ENoInit NoInit)
{
}
Epic Games определяет подобное enum
, но добавляет избыточные имена enum
:
enum EForceInit
{
ForceInit,
ForceInitToZero
};
enum ENoInit {NoInit};
Передача NoInit
в конструктор FHitResult
предотвращает инициализацию, что может привести к увеличению производительности, если не инициализировать значения, которые будут инициализированы в другом месте.
FHitResult(NoInit)
Использование в DamirH посте в серии анализов всестороннего игрового процесса:
//A struct for temporary holding of actors (and transforms) of actors that we hit
//that don't have an ASC. Used for environment impact GameplayCues.
struct FNonAbilityTarget
{
FGameplayTagContainer CueContainer;
TWeakObjectPtr<AActor> TargetActor;
FHitResult TargetHitResult;
bool bHasHitResult;
public:
FNonAbilityTarget()
: CueContainer(FGameplayTagContainer())
, TargetActor(nullptr)
, TargetHitResult(FHitResult(ENoInit::NoInit))
, bHasHitResult(false)
{
}
// (…)