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

Static const Member Значение vs Member перечисление: какой метод лучше и почему?

Если вы хотите связать какое-то постоянное значение с классом, вот два способа достижения одной и той же цели:

class Foo
{
public:
    static const size_t Life = 42;
};

class Bar
{
public:
    enum {Life = 42};
};

Синтаксически и семантически они кажутся идентичными с клиентской точки зрения:

size_t fooLife = Foo::Life;
size_t barLife = Bar::Life;

Есть ли какая-то другая причина, кроме только чистого стиля, почему вы предпочитаете другого?

4b9b3361

Ответ 1

Ранг enum был необходим, потому что многие компиляторы не поддерживали инициализацию значения на месте. Поскольку это уже не проблема, перейдите к другому варианту. Современные компиляторы также могут оптимизировать эту константу, так что для нее не требуется место для хранения.

Единственная причина, по которой не использовать вариант static const, - это если вы хотите запретить принимать адрес значения: вы не можете взять адрес значения enum, в то время как вы можете принять адрес константы ( и это побудило компилятор зарезервировать место для значения в конце концов, но только если его адрес действительно принят).

Кроме того, получение адреса приведет к ошибке времени ссылки, если явно не определена константа. Обратите внимание, что он все равно может быть инициализирован на сайте объявления:

struct foo {
    static int const bar = 42; // Declaration, initialization.
};

int const foo::bar; // Definition.

Ответ 2

Они не идентичны:

size_t *pLife1 = &Foo::Life;
size_t *pLife2 = &Bar::Life;

Ответ 3

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

Ответ 4

Ну, если нужно, вы можете взять адрес статического значения константы-члена. Вы должны объявить отдельную переменную-члену типа перечисления, чтобы принять его адрес.

Ответ 5

Другое третье решение?

Одно тонкое отличие состоит в том, что перечисление должно быть определено в заголовке и видимым для всех. Когда вы избегаете зависимостей, это боль. Например, в PImpl добавление перечисления несколько контрпродуктивно:

// MyPImpl.hpp

class MyImpl ;

class MyPimpl
{
   public :
      enum { Life = 42 } ;
   private :
      MyImpl * myImpl ;
}

Другим третьим решением будет вариант альтернативы "const static", предложенный в вопросе: Объявление переменной в заголовке, но определение ее в источнике:

// MyPImpl.hpp

class MyImpl ;

class MyPimpl
{
   public :
      static const int Life ;
   private :
      MyImpl * myImpl ;
}

.

// MyPImpl.cpp
const int MyPImpl::Life = 42 ;

Обратите внимание, что значение MyPImpl:: Life скрыто от пользователя MyPImpl (который включает MyPImpl.hpp).

Это позволит автору MyPimpl изменять значение "Life" по мере необходимости, без необходимости повторного компиляции пользователя MyPImpl, как и общая цель PImpl.

Ответ 6

Значения

static const считаются значениями r, как и enum, в 99% кода, который вы увидите. Постоянные значения r никогда не имеют памяти для них. Преимущество констант enum заключается в том, что они не могут стать l-значениями в этом другом 1%. Значения static const являются безопасными по типу и допускают поплавки, c-строки и т.д.

Компилятор сделает Foo::Life значение l, если оно связано с памятью. Обычный способ сделать это - принять его адрес. например &Foo::Life;

Вот пример, где GCC будет использовать адрес:

int foo = rand()? Foo::Life: Foo::Everthing;

Сгенерированный код компилятора использует адреса Life и Everything. Хуже того, это приводит к ошибке компоновщика в отношении недостающих адресов для Foo::Life и Foo::Everything. Это поведение полностью стандартно соответствует, хотя, очевидно, нежелательно. Существуют другие способы компиляции, которые могут произойти, и все стандартные соответствия.

Как только у вас будет соответствующий компилятор С++ 11, правильный код будет

class Foo {
 public:
  constexpr size_t Life = 42;
};

Это гарантировано всегда будет l-значением и безопасным по типу, лучшим из обоих миров.

Ответ 7

Перехват перечислений стоит знать по нескольким причинам. Во-первых, перехват перехвата ведет себя в некотором смысле скорее как #define, чем const, а иногда и то, что вы хотите. Например, легально принять адрес const, но неправомерно принять адрес перечисления, и он обычно не является законным для адреса адреса #define. Если вы не хотите, чтобы люди получали указатель или ссылку на одну из ваших интегральных констант, перечисление является хорошим способом принудительного применения этого ограничения. (Более подробно об использовании ограничений проектирования с помощью решений по кодированию см. Пункт 18.) Кроме того, хотя хорошие компиляторы не будут выделять хранилище для объектов const интегральных типов (если вы не создаете указатель или ссылку на объект), неактивные компиляторы могут, и вы можете не захотеть выделить память для таких объектов. Как и #defines, перечисления никогда не приводят к ненужному распределению памяти.

Эффективная книга С++