Почему я не могу инициализировать неконстантный static
член или static
массив в классе?
class A
{
static const int a = 3;
static int b = 3;
static const int c[2] = { 1, 2 };
static int d[2] = { 1, 2 };
};
int main()
{
A a;
return 0;
}
компилятор выдает следующие ошибки:
g++ main.cpp
main.cpp:4:17: error: ISO C++ forbids in-class initialization of non-const static member ‘b’
main.cpp:5:26: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:5:33: error: invalid in-class initialization of static data member of non-integral type ‘const int [2]’
main.cpp:6:20: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:6:27: error: invalid in-class initialization of static data member of non-integral type ‘int [2]’
У меня два вопроса:
- Почему я не могу инициализировать
static
элементы данных в классе? - Почему я не могу инициализировать
static
массивы в классе, дажеconst
массив?
Ответы:
Почему я не могу инициализировать
static
элементы данных в классе?Стандарт C ++ позволяет инициализировать внутри класса только статические постоянные целочисленные или перечисляемые типы. Это причина, по
a
которой разрешена инициализация, а другим нет.Ссылка:
C ++ 03 9.4.2 Статические элементы данных
§4
Что такое целочисленные типы?
C ++ 03 3.9.1 Основные типы
§7
Сноска:
Обходной путь:
Вы можете использовать трюк с перечислением для инициализации массива внутри определения вашего класса.
Почему Стандарт не допускает этого?
Бьярн хорошо объясняет это здесь :
Почему
static const
разрешены только интегральные типы и перечисления в инициализации класса?Ответ скрыт в цитате Бьярна, прочтите ее внимательно:
«C ++ требует, чтобы каждый объект имел уникальное определение. Это правило было бы нарушено, если бы C ++ разрешил определение сущностей внутри класса, которые необходимо хранить в памяти как объекты».
Обратите внимание, что только
static const
целые числа можно рассматривать как константы времени компиляции. Компилятор знает, что целочисленное значение не изменится в любое время, и, следовательно, он может применять свою собственную магию и применять оптимизацию, компилятор просто встраивает такие члены класса, то есть они больше не хранятся в памяти, поскольку необходимость хранения в памяти устранена , это дает таким переменным исключение из правила, упомянутого Бьярном.Здесь следует отметить, что даже если
static const
целочисленные значения могут иметь инициализацию класса, получение адреса таких переменных не допускается. Можно взять адрес статического члена, если (и только если) он имеет внеклассовое определение. Это дополнительно подтверждает рассуждение выше.перечисления разрешены, потому что значения перечислимого типа могут использоваться там, где ожидаются целые числа. см. цитату выше
Как это изменилось в C ++ 11?
C ++ 11 до некоторой степени ослабляет ограничение.
C ++ 11 9.4.2 Статические элементы данных
§3
Кроме того , C ++ 11 будет разрешать (§12.6.2.8) нестатический член данных должны быть инициализированы , где она объявлена (в своем классе). Это будет означать очень простую пользовательскую семантику.
Обратите внимание, что эти функции еще не реализованы в последней версии gcc 4.7, поэтому вы все равно можете получать ошибки компиляции.
источник
&member
вернется?static const char*
член?Кажется, это пережиток старых времен простых линкеров. Вы можете использовать статические переменные в статических методах как временное решение:
и
и
сборки:
бегать:
Тот факт, что это работает (последовательно, даже если определение класса включено в разные единицы компиляции), показывает, что современный компоновщик (gcc 4.9.2) на самом деле достаточно умен.
Забавно: Печать
0123
на руке и3210
на x86.источник
Я думаю, это для того, чтобы вы не смешивали декларации и определения. (Подумайте о проблемах, которые могут возникнуть, если вы разместите файл в нескольких местах.)
источник
Это потому, что может быть только одно определение,
A::a
которое используют все единицы перевода.Если вы выступали
static int a = 3;
в классе с заголовком, включенным во все единицы перевода, вы получите несколько определений. Таким образом, определение статического значения, не относящееся к внешнему, принудительно вызывает ошибку компилятора.Использование
static inline
илиstatic const
исправление этого.static inline
конкретизирует символ только в том случае, если он используется в единице трансляции, и гарантирует, что компоновщик выбирает и оставляет только одну копию, если она определена в нескольких единицах трансляции из-за того, что она находится в группе comdat.const
в области видимости файла заставляет компилятор никогда не выдавать символ, потому что он всегда немедленно заменяется в коде, если онextern
не используется, что недопустимо в классе.Следует отметить, что
static inline int b;
это рассматривается как определение, тогда какstatic const int b
илиstatic const A b;
все еще рассматриваются как объявление и должны быть определены вне очереди, если вы не определяете его внутри класса. Интересноstatic constexpr A b;
трактуется как определение, тогдаstatic constexpr int b;
как это ошибка и должна иметь инициализатор (это потому, что теперь они становятся определениями и, как любое определение const / constexpr в области файла, им требуется инициализатор, которого нет у int, но тип класса делает, потому что он имеет неявное значение,= A()
когда является определением - clang позволяет это, но gcc требует, чтобы вы явно инициализировали, иначе это ошибка. Вместо этого это не проблема с inline).static const A b = A();
не допускается и должно бытьconstexpr
илиinline
чтобы разрешить инициализатор для статического объекта с типом класса, то есть сделать статический член типа класса более чем декларацией. Итак, да в определенных ситуацияхA a;
- это не то же самое, что явная инициализацияA a = A();
(первое может быть объявлением, но если для этого типа разрешено только объявление, то второе является ошибкой. Второе может использоваться только в определении.constexpr
Делает его определением ). Если вы используетеconstexpr
и указываете конструктор по умолчанию, то конструктор должен бытьconstexpr
Статический член - это прямая декларация области видимости файла
extern int A::a;
(которая может быть сделана только в классе, а определения вне линии должны относиться к статическому члену в классе и должны быть определениями и не могут содержать extern), тогда как нестатический член является частью полное определение типа класса и имеют те же правила, что и объявления области видимости файла безextern
. Это неявные определения. Такint i[]; int i[5];
что это переопределение, тогда какstatic int i[]; int A::i[5];
это не так, но в отличие от двух externs, компилятор все равно обнаружит повторяющийся член, если вы это сделаетеstatic int i[]; static int i[5];
в классе.источник
статические переменные относятся к классу. Конструкторы инициализируют атрибуты ОСОБЕННО для экземпляра.
источник