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

почему я не могу инициализировать non-const 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]’

у меня два вопроса:

  1. почему я не могу инициализировать static данные-члены класса?
  2. почему я не могу инициализировать static массивы в классе, даже const массив?

4 ответов


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

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

ссылки:
в C++03 9.4.2 статические данные-члены
§4

если статический элемент данных имеет тип перечисления const integral или const, его объявление в определение класса может указывать константу-инициализатор, которая должна быть интегральным постоянным выражением (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 trick для инициализации массива внутри определения класса.

class A 
{
    static const int a = 3;
    enum { arrsize = 2 };

    static const int c[arrsize] = { 1, 2 };

};

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

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

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

почему только static const integral types & enums разрешена инициализация в классе?

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

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

примечательно, что даже если static const интегральные значения могут иметь инициализацию в классе, принятие адреса таких переменных не допускается. Можно взять адрес статического члена, если (и только если) он имеет внеклассовое определение.Это еще раз подтверждает приведенные выше доводы.

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


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

C++11 в определенной степени ослабляет ограничение.

в C++11 9.4.2 статические данные-члены
§3

если статический элемент данных имеет тип литерала const, его объявление в определении класса может указать brace-or-equal-initializer, в которой каждый инициализатор-п. это назначение-выражение является постоянным выражением. Статический элемент данных литерального типа может быть объявлен в определении класса с помощью constexpr specifier; если да, то в его объявлении указывается brace-or-equal-initializer, в которой каждый инициализатор-п. что это назначение-выражение является постоянным выражением. [ Примечание: в обоих этих случаях член может отображаться в постоянных выражениях. - примечание в конце ] член по-прежнему определяется в область пространства имен, если она используется в программе и определение области пространства имен не должно содержать инициализатор.

Кроме Того, 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;
}

build:

g++ -std=gnu++0x -save-temps=obj -c compilation_unit_a.cxx 
g++ -std=gnu++0x -o main main.cxx compilation_unit_a.o

run:

./main

тот факт, что это работает (последовательно, даже если определение класса включено в разные единицы компиляции), показывает, что компоновщик сегодня (gcc 4.9.2) на самом деле достаточно умен.

Смешно: Принты 0123 на руки и 3210 на x86.


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


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