Почему я не могу инициализировать неконстантный статический член или статический массив в классе?
Почему я не могу инициализировать неконстантный 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
массив?
4 ответа
Почему я не могу инициализировать static
члены данных в классе?
Стандарт C++ допускает инициализацию внутри класса только статических интегральных или перечислимых типов. Это причина a
разрешено инициализировать, а другие нет.
Ссылка:
C++ 03 9.4.2 Статические члены данных
§4
Если член статических данных имеет константный интеграл или константный тип перечисления, его объявление в определении класса может указывать инициализатор константы, который должен быть выражением интегральной константы (5.19). В этом случае член может появляться в виде целочисленных константных выражений. Член по-прежнему должен быть определен в области пространства имен, если он используется в программе и определение области пространства имен не должно содержать инициализатор.
Что такое целочисленные типы?
C++ 03 3.9.1 Основные типы
§7
Типы bool, char, wchar_t, а также целочисленные типы со знаком и без знака вместе называются целочисленными типами.43) Синонимом целочисленного типа является целочисленный тип.
Сноска:
43) Следовательно, перечисления (7.2) не являются целочисленными; однако перечисления могут быть переведены в int, unsigned int, long или unsigned long, как указано в 4.5.
Временное решение:
Вы можете использовать трюк enum для инициализации массива внутри определения вашего класса.
class A
{
static const int a = 3;
enum { arrsize = 2 };
static const int c[arrsize] = { 1, 2 };
};
Почему стандарт не позволяет этого?
Бьярне объясняет это удачно здесь:
Класс обычно объявляется в файле заголовка, а файл заголовка обычно включается во многие единицы перевода. Однако, чтобы избежать сложных правил компоновщика, C++ требует, чтобы у каждого объекта было уникальное определение. Это правило было бы нарушено, если бы C++ позволял в классе определять сущности, которые нужно было хранить в памяти как объекты.
Почему только static const
допускаются целочисленные типы и перечисления
Ответ скрыт в цитате Бьярне, прочитайте его внимательно,
"C++ требует, чтобы у каждого объекта было уникальное определение. Это правило было бы нарушено, если бы C++ допускал определение сущностей в классе, которые должны были храниться в памяти как объекты".
Обратите внимание, что только static const
целые числа могут рассматриваться как константы времени компиляции. Компилятор знает, что целочисленное значение не изменится в любое время и, следовательно, он может применить свою собственную магию и применить оптимизации, компилятор просто вставляет такие члены класса, т. Е. Они больше не сохраняются в памяти, так как необходимость сохранения в памяти устраняется он дает таким переменным исключение из правила, упомянутого Бьярне.
Следует отметить, что даже если static const
целочисленные значения могут иметь Инициализацию в классе, взятие адреса таких переменных не допускается. Можно взять адрес статического члена, если (и только если) он имеет внеклассовое определение. Это дополнительно подтверждает рассуждения выше.
перечисления допускаются, потому что значения перечислимого типа могут использоваться там, где ожидаются целые числа. см. цитату выше
Как это меняется в C++ 11?
C++ 11 ослабляет ограничение в определенной степени.
C++ 11 9.4.2 Элементы статических данных
§3
Если член статических данных имеет константный литеральный тип, его объявление в определении класса может указывать инициализатор скобок или равных, в котором каждое предложение инициализатора, являющееся выражением присваивания, является константным выражением. Статический член данных литерального типа может быть объявлен в определении класса с помощью
constexpr specifier;
если это так, его объявление должно указывать инициализатор скобок или равных, в котором каждое предложение инициализатора, являющееся выражением присваивания, является константным выражением. [Примечание: в обоих этих случаях член может появляться в константных выражениях. - примечание конца] Элемент все еще должен быть определен в области пространства имен, если он используется в программе, и определение области пространства имен не должно содержать инициализатор.
Кроме того, C++11 позволит (§12.6.2.8) нестатический член данных быть инициализированным там, где он объявлен (в своем классе). Это будет означать гораздо более легкую пользовательскую семантику.
Обратите внимание, что эти функции еще не были реализованы в последней версии gcc 4.7, поэтому вы все равно можете получить ошибки компиляции.
Это кажется пережитком старых времен простых линкеров. В качестве обходного пути вы можете использовать статические переменные в статических методах:
// header.hxx
#include <vector>
class Class {
public:
static std::vector<int> & replacement_for_initialized_static_non_const_variable() {
static std::vector<int> Static {42, 0, 1900, 1998};
return Static;
}
};
int compilation_unit_a();
а также
// compilation_unit_a.cxx
#include "header.hxx"
int compilation_unit_a() {
return Class::replacement_for_initialized_static_non_const_variable()[1]++;
}
а также
// main.cxx
#include "header.hxx"
#include <iostream>
int main() {
std::cout
<< compilation_unit_a()
<< Class::replacement_for_initialized_static_non_const_variable()[1]++
<< compilation_unit_a()
<< Class::replacement_for_initialized_static_non_const_variable()[1]++
<< std::endl;
}
построить:
g++ -std=gnu++0x -save-temps=obj -c compilation_unit_a.cxx
g++ -std=gnu++0x -o main main.cxx compilation_unit_a.o
бежать:
./main
Тот факт, что это работает (последовательно, даже если определение класса включено в разные блоки компиляции), показывает, что компоновщик сегодня (gcc 4.9.2) на самом деле достаточно умен.
Смешно: принты 0123
на руку и 3210
на х86.
Это потому, что может быть только одно определение 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
#include<iostream>
struct A
{
int b =2;
mutable int c = 3; //if this member is included in the class then const A will have a full .data symbol emitted for it on -O0 and so will B because it contains A.
static const int a = 3;
};
struct B {
A b;
static constexpr A c; //needs to be constexpr or inline and doesn't emit a symbol for A a mutable member on any optimisation level
};
const A a;
const B b;
int main()
{
std::cout << a.b << b.b.b;
return 0;
}
Статический член - это прямое объявление области файла extern int A::a;
(который может быть сделан только в классе, а определения вне линии должны относиться к статическому члену в классе и должны быть определениями и не могут содержать extern), тогда как нестатический член является частью полного определения типа класса и имеет те же правила, что и объявления области действия файла без extern
. Это неявные определения. Такint i[]; int i[5];
это переопределение, тогда как static int i[]; int A::i[5];
нет, но в отличие от двух внешних, компилятор все равно обнаружит повторяющийся член, если вы это сделаете static int i[]; static int i[5];
в классе.
Я думаю, что это мешает вам смешивать декларации и определения. (Подумайте о проблемах, которые могут возникнуть, если вы включите файл в нескольких местах.)
Статические переменные являются специфическими для класса. Конструкторы инициализируют атрибуты ESPECIALY для экземпляра.