Почему у нас не может быть неконстантных статических переменных на уровне класса? - PullRequest
3 голосов
/ 17 октября 2011

Почему компилятор Visual C ++ отказывается компилировать этот код ?

Я, очевидно, знаю, что ошибка:

Ошибка C2864: Singleton<T>::p:
В классе

* 1011 могут быть инициализированы только статические члены-интегралы с постоянными данными.*

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

Также кажется, что не все компиляторы возражают против этого .

Кроме того, как правильно исправить это?

template<typename T>
struct Singleton
{
    static T *p = 0;  // Error C2864

    static T *getInstance() { /*...*/ return p; }
};

Ответы [ 7 ]

9 голосов
/ 17 октября 2011

Это стандартное поведение. Только статические интегральные члены const могут быть инициализированы без правильного определения. Все остальные типы должны быть где-то определены, а инициализация записана в точке определения:

template<typename T>
struct Singleton {
    static T *p;

    static T *getInstance() { /*...*/ return p; }
};

template<typename T>
T *Singleton<T>::p = 0;

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

4 голосов
/ 17 октября 2011

Как все отметили, вы не можете определить неконстантный, нецелый тип в теле класса (по крайней мере, не в C ++ 03, он изменился в C ++ 11, но я не уверен, какименно так).Тем не менее, вы можете сделать это по-другому и чисто.

template<typename T>
struct Singleton {
    static T* getInstance() {
        static T* p = NULL;
        /*...*/
        return p;
    }
};
4 голосов
/ 17 октября 2011

Вы можете иметь этот тип переменной, но вы не можете инициализировать ее внутри определения класса. Единственный тип переменной, который может быть инициализирован так, как вы спрашиваете, это static const.

Фиксированная версия определения вашего класса удаляет = 0 и добавляет его ниже определения класса:

template<typename T>
T *Singleton<T>::p = 0;

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

3 голосов
/ 17 октября 2011

Объявления предназначены для добавления в заголовочные файлы, где они будут компилироваться много раз - в каждом месте, куда они включены.

Статические переменные должны иметь только одно определение, чтобы во всей программе существовала только одна копия. Это означает, что он должен быть в исходном (.cpp) файле. Присвоение значения необходимо поставить в этом месте.

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

1 голос
/ 17 октября 2011

Интересный вопрос не тот, который вы задаете, а наоборот:

Почему константным интегральным статическим элементам разрешено присваивать значение в объявлении ?

Важным вопросом является объявление .Значение, которое получает переменная, задается в переменной definition , и это согласуется с неконстантными или нецелыми статическими членами класса, где в классе вы предлагаете только объявление .Значение инициализатора предоставляется в определении , которое находится за пределами определения класса, обычно в .cpp, который гарантирует, что оно будет определено в одной единице перевода.

Нопочему интегральные статические константы могут иметь значение в объявлении?

По практическим соображениям.Компилятор может использовать интегральные константы как константы времени компиляции , то есть он может фактически заменить значение константы вместо идентификатора во всех местах, где константа используется как rvalue , например, при определении размера массива.Но если значение присутствует только в определении в одной единице перевода, компилятор не сможет использовать ее во всех других единицах перевода.В качестве примера:

// fq.h
struct fixed_queue {
   static const std::size_t max_elements; // [1]
   int data[ max_elements ];              // Error: How big is data??
};
// fq.cpp
#include "fq.h"
const std::size_t fixed_queue::max_elements = 10;

Если бы max_elements не было разрешено иметь значение в объявлении [1], то вы не сможете использовать эту константу для определения размера массива data, что вполне разумно использовать для статической константы.

Почему бы не распространить это на все другие случаи?

Потому что, похоже, это не имеет особого смысла... Предоставление значения в определении класса не может использоваться компилятором ни при каких других обстоятельствах, и, следовательно, оно не требуется, поэтому только интегральные константы должны обрабатываться по-разному.

1 голос
/ 17 октября 2011

Вы не можете назначить нестатик в теле класса таким образом. Вместо этого присвойте значение за пределами класса (обычно в вашем файле cpp)

template<typename T>
struct Singleton {
    static T *p;

    static T *getInstance() { /*...*/ return p; }
};

template<typename T>
T *Singleton<T>::p = 0;
0 голосов
/ 17 октября 2011

Причина в том, что нецелые, неконстантные значения требуют места в памяти.

const int может быть обработан статически компилятором и встроен непосредственно в определенные машинные инструкции, плавающие объекты и более экзотические объекты должны где-то жить, потому что машинные инструкции будут работать только с точки зрения их адреса.

В принципе, язык мог бы допустить это, но это означало бы либо создание дополнительных объектов и раздувание двоичного файла (хорошо для consts), либо усложнение жизни создателей компилятора для non-consts: избыточные копии должны быть упал, чтобы сохранить правило с одним определением (кстати, это то, что нужно делать шаблонам).

...