Почему я не могу инициализировать нестационарный статический член или статический массив в классе?

Почему я не могу инициализировать массив non-const static member или 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?

Ответ 1

Почему я не могу инициализировать членов static в классе?

Стандарт С++ позволяет инициализировать только статические константные интегральные или перечисляемые типы внутри класса. По этой причине a разрешено инициализировать, а другие нет.

Справка:
С++ 03 9.4.2 Элементы статических данных
§ 4

Если статический член данных имеет тип const const или const, его объявление в определении класса может указывать константу-инициализатор, который должен быть интегральным постоянным выражением (5.19). В этом случае член может фигурировать в интегральных постоянных выражениях. Член все еще должен быть определен в области пространства имен, если он используется в программе, и определение области пространства имен не должно содержать инициализатор.

Что такое интегральные типы?

С++ 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 };

};

Почему стандарт не позволяет это?

Bjarne объясняет это метко здесь:

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

Почему только static const интегральные типы и перечисления разрешены In-class Initialization?

Ответ скрыт в цитате Бьярне, внимательно прочитайте его,
"С++ требует, чтобы каждый объект имел уникальное определение. Это правило было бы нарушено, если бы С++ допускал определение класса объектов в классе, которое нужно было хранить в памяти как объекты".

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

Следует отметить здесь, что даже если интегральные значения static const могут иметь In-Class Initialization, использование адреса таких переменных недопустимо. Можно принять адрес статического члена, если (и только если) имеет определение вне класса. Это дополнительно подтверждает обоснование выше.

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


Как это изменяется в С++ 11?

С++ 11 смягчает ограничение до определенной степени.

С++ 11 9.4.2 Элементы статических данных
§3

Если статический член данных имеет тип const literal, его объявление в определении класса может указывать логический или равный-инициализатор, в котором каждое предложение-инициализатор, являющееся выражением-присваивания, является постоянным выражением. Статический член данных типа literal может быть объявлен в определении класса с помощью constexpr specifier;, если это так, в его декларации указывается логический или равный-инициализатор, в котором каждое предложение-инициализатор, являющееся выражением-присваивания, является константным выражением, [Примечание. В обоих случаях член может отображаться в постоянных выражениях. -end note] Член все еще должен быть определен в области пространства имен, если он используется в программе, а определение области пространства имен не должно содержать инициализатор.

Кроме того, С++ 11 позволит (§12.6.2.8) нестатический член данных инициализироваться там, где он объявлен (в своем классе). Это будет означать очень легкую пользовательскую семантику.

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

Ответ 2

Это кажется реликтом из старых дней простых линкеров. Вы можете использовать статические переменные в статических методах в качестве обходного пути:

// 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 на x86.

Ответ 3

Я думаю, что это мешает вам смешивать декларации и определения. (Подумайте о проблемах, которые могут возникнуть, если вы включите файл в несколько мест.)

Ответ 4

Статические переменные

относятся к классу. Конструкторы инициализируют атрибуты ESPECIALY для экземпляра.