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

Итак, допустим, у меня есть такой заголовок:

#ifndef BASECLASS_H
#define BASECLASS_H

class BaseClass
{
    public:
        static int getX(){return x;}
    private:
        static int x;
};

int BaseClass::x = 10;

#endif

Я много раз слышал, что мне следует инициализировать статические переменные не внутри заголовка, а в cpp. Но поскольку есть охранники, должна быть только одна копия BaseClass::x. Так что я вроде как не понимаю, зачем мне ставить

int BaseClass::x = 10; 

в cpp.


person user3496846    schedule 04.04.2014    source источник
comment
Формулировка вопроса и ответы здесь лучше, чем в номинированном вопросе!   -  person Davis Herring    schedule 18.09.2017


Ответы (4)


Если вы сделаете это в заголовке, вы получите несколько ошибок определения, как только включите его из более чем одного файла CPP. Вы действительно говорите компилятору две вещи, когда объявляете

int BaseClass::x = 10;

Сначала вы определяете символ BaseClass :: x; во-вторых, вы говорите, что хотите, чтобы у него было начальное значение 10. Согласно правилу одного определения это может произойти только один раз в вашей программе.

person Nathan Monteleone    schedule 04.04.2014
comment
Думаю, я начинаю понимать эту идею, но есть ли способ ее продемонстрировать? Мне нужно написать две разные программы, которые включают BaseClass, и запускать их одновременно? - person user3496846; 04.04.2014
comment
@ user3496846 нет, вам просто нужно иметь одну программу, состоящую из (как минимум) двух файлов cpp, оба из которых включают ваш заголовок. - person lethal-guitar; 04.04.2014

Может быть, это легче понять, если подумать о том, что на самом деле делает препроцессор: он копирует содержимое всех включенных файлов заголовков в файл cpp и передает его компилятору.

Теперь предположим, что у вас есть:

// In a.cpp
#include <baseclass.h>

// more code

// In b.cpp
#include <baseclass.h>

// more code

После того, как препроцессор расширит включения, оба файла будут содержать:

int BaseClass::x = 10; 

Теперь, как только оба объектных файла будут переданы компоновщику, он дважды увидит символ BaseClass::x, что является ошибкой.

Теперь, чтобы сделать это еще более очевидным, представьте, что вы поместите это в файл заголовка:

int aGlobalVariable = 10;

А затем включите его в два разных файла cpp, которые оба должны быть связаны в один исполняемый файл. На самом деле он ничем не отличается от вашего примера, если смотреть с точки зрения компоновщика.

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

Есть разница между объявлениями и определениями. Только последнее вызовет проблемы. Например, все следующие объявления:

  • extern int a;
  • void foo(int a);
  • class Foo { int bar(); };

Принимая во внимание, что это определения:

  • int a;
  • int b = 10;
  • void foo(int a) { /*..*/ }
  • int Foo::bar() { /*...*/ }

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

А что насчет классов? Классы можно только объявлять, а их функции-члены и статические члены должны быть определены. Опять же, каждое определение может существовать только один раз.

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

Возвращаясь к вашей конкретной проблеме: статические члены - это в основном просто глобальные переменные, но привязанные к имени класса.

Надеюсь, это проясняет вам ситуацию!

person lethal-guitar    schedule 04.04.2014
comment
Но помимо #include baseClass.h, не будет ли компоновщик также включать двойное определение самого класса, что также должно вызвать проблему? Извините, мне нужно время, чтобы понять ... - person user3496846; 04.04.2014
comment
Нет, потому что это просто объявление класса объявление. Я добавлю немного об этом к своему ответу - person lethal-guitar; 04.04.2014
comment
Хорошо, большое спасибо за такой информативный ответ, думаю, я начинаю понимать :) - person user3496846; 05.04.2014

Охранники не препятствуют множеству копий в нескольких исходных файлах. Они предотвращают только несколько копий в одном исходном файле.

Вы нарушите одно правило определения, если у вас есть несколько исходных файлов, #include "base_class.h".

person David Hammen    schedule 04.04.2014

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

person 06needhamt    schedule 04.04.2014