static const Member Value против Member enum: какой метод лучше и почему? - PullRequest
42 голосов
/ 15 октября 2008

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

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;

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

Ответы [ 7 ]

56 голосов
/ 15 октября 2008

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

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

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

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

int const foo::bar; // Definition.
11 голосов
/ 15 октября 2008

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

size_t *pLife1 = &Foo::Life;
size_t *pLife2 = &Bar::Life;
9 голосов
/ 15 октября 2008

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

7 голосов
/ 17 октября 2008

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

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

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

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

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

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

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

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

5 голосов
/ 16 октября 2008

Еще одно третье решение?

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

// 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.

5 голосов
/ 15 октября 2008

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

3 голосов
/ 24 февраля 2013

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

Ectivetive C ++ Book

...