Почему я не могу инициализировать неконстантный статический член или статический массив в классе? - PullRequest
91 голосов
/ 11 марта 2012

Почему я не могу инициализировать неконстантный 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 ]

124 голосов
/ 11 марта 2012

Почему я не могу инициализировать 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 trick чтобы инициализировать массив внутри определения вашего класса.

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

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

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

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

3 голосов
/ 24 июля 2015

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

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

1 голос
/ 11 марта 2012

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

0 голосов
/ 23 марта 2015

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...