class A {
static int foo () {} // ok
static int x; // <--- needed to be defined separately in .cpp file
};
Я не вижу необходимости A::x
определять отдельно в файле .cpp (или тот же файл для шаблонов). Почему нельзя A::x
объявить и определить одновременно?
Было ли это запрещено по историческим причинам?
Мой главный вопрос: повлияет ли это на какую-либо функциональность, если static
элементы данных были объявлены / определены одновременно (так же, как Java )?
c++
data
language-features
grammar
static-access
iammilind
источник
источник
inline static int x[] = {1, 2, 3};
. См. En.cppreference.com/w/cpp/language/static#Static_data_membersОтветы:
Я думаю, что ограничение, которое вы рассмотрели, связано не с семантикой (почему что-то должно измениться, если инициализация была определена в том же файле?), А с моделью компиляции C ++, которая по причинам обратной совместимости не может быть легко изменена, поскольку либо станут слишком сложными (поддерживая новую модель компиляции и существующую одновременно), либо не позволят скомпилировать существующий код (путем введения новой модели компиляции и удаления существующей).
Модель компиляции C ++ основана на модели C, в которой вы импортируете объявления в исходный файл, включая файлы (заголовков). Таким образом, компилятор видит ровно один большой исходный файл, содержащий все включенные файлы и все файлы, включенные из этих файлов, рекурсивно. Это имеет одно большое преимущество IMO, а именно то, что он облегчает реализацию компилятора. Конечно, вы можете написать что-нибудь во включенных файлах, то есть как объявления, так и определения. Хорошей практикой является размещение объявлений в заголовочных файлах и определений в файлах .c или .cpp.
С другой стороны, возможно иметь модель компиляции, в которой компилятор очень хорошо знает, импортирует ли он объявление глобального символа, определенного в другом модуле , или компилирует определение глобального символа, предоставленного текущий модуль . Только в последнем случае компилятор должен поместить этот символ (например, переменную) в текущий объектный файл.
Например, в GNU Pascal вы можете записать модуль
a
в файлa.pas
следующим образом:где глобальная переменная объявлена и инициализирована в том же исходном файле.
Тогда у вас могут быть разные модули, которые импортируют a и используют глобальную переменную
MyStaticVariable
, например, единица b (b.pas
):и блок с (
c.pas
):Наконец, вы можете использовать блоки b и c в основной программе
m.pas
:Вы можете скомпилировать эти файлы отдельно:
и затем создайте исполняемый файл с:
и запустить его:
Хитрость здесь в том , что когда компилятор видит использование директивы в программном модуле (например , использует в b.pas), она не включает в себя соответствующий .pas файл, но выглядит для .gpi файла, т.е. для предварительно скомпилированных файл интерфейса (см. документацию ). Эти
.gpi
файлы генерируются компилятором вместе с.o
файлами при компиляции каждого модуля. Таким образом, глобальный символMyStaticVariable
определяется только один раз в объектном файлеa.o
.Java работает аналогичным образом: когда компилятор затем импортирует класс A в класс B, он ищет файл класса для A и не нуждается в этом файле
A.java
. Таким образом, все определения и инициализации для класса A могут быть помещены в один исходный файл.Возвращаясь к C ++, причина, по которой в C ++ вы должны определять статические элементы данных в отдельном файле, больше связана с моделью компиляции C ++, чем с ограничениями, налагаемыми компоновщиком или другими инструментами, используемыми компилятором. В C ++ импорт некоторых символов означает создание их объявления как части текущего модуля компиляции. Это очень важно, среди прочего, из-за способа компиляции шаблонов. Но это означает, что вы не можете / не должны определять какие-либо глобальные символы (функции, переменные, методы, члены статических данных) во включаемом файле, в противном случае эти символы могут быть многократно определены в скомпилированных объектных файлах.
источник
Поскольку статические члены являются общими для ВСЕХ экземпляров класса, они должны быть определены в одном и только одном месте. Действительно, это глобальные переменные с некоторыми ограничениями доступа.
Если вы попытаетесь определить их в заголовке, они будут определены в каждом модуле, который включает этот заголовок, и вы получите ошибки во время компоновки, поскольку он найдет все дубликаты определений.
Да, это, по крайней мере, частично историческая проблема, связанная с cfront; может быть написан компилятор, который создаст некий скрытый «static_members_of_everything.cpp» и ссылку на него. Однако это нарушило бы обратную совместимость, и никакой реальной выгоды от этого не было бы.
источник
static
переменные объявлены / определены в одном месте (например, в Java), то что может пойти не так?static
участниковtemplate
? Они разрешены во всех заголовочных файлах, так как они должны быть видны. Я не оспариваю этот ответ, но он также не соответствует моему вопросу.Вероятная причина этого заключается в том, что это позволяет поддерживать язык C ++ в тех средах, где объектный файл и модель связей не поддерживают слияние нескольких определений из нескольких объектных файлов.
Объявление класса (называемое объявлением по уважительным причинам) помещается в несколько единиц перевода. Если бы в объявлении содержались определения для статических переменных, то вы бы получили несколько определений в нескольких единицах перевода (и помните, что эти имена имеют внешнюю связь).
Такая ситуация возможна, но требует, чтобы компоновщик обрабатывал несколько определений без жалоб.
(И обратите внимание, что это противоречит Правилу Одного Определения, если это не может быть сделано в соответствии с типом символа или типом раздела, в котором он размещен.)
источник
Существует большая разница между C ++ и Java.
Java работает на своей собственной виртуальной машине, которая создает все в собственной среде выполнения. Если определение встречается более одного раза, оно просто воздействует на один и тот же объект, который в конечном итоге знает среда выполнения.
В C ++ нет «конечного владельца знаний»: C ++, C, Fortran Pascal и т. Д. Все являются «переводчиками» из исходного кода (файла CPP) в промежуточный формат (файл OBJ или файл «.o», в зависимости от ОС), где операторы переводятся в машинные инструкции, а имена становятся косвенными адресами, опосредованными таблицей символов.
Программа создается не компилятором, а другой программой («компоновщиком»), которая объединяет все OBJ (независимо от языка, с которого они происходят), перенаправляя все адреса, которые относятся к символам, к их эффективное определение.
По тому, как работает компоновщик, определение (что создает физическое пространство для переменной) должно быть уникальным.
Обратите внимание, что C ++ сам по себе не связывает, и что компоновщик не выпускается спецификациями C ++: компоновщик существует из-за способа сборки модулей ОС (обычно в C и ASM). С ++ должен использовать это так, как есть.
Теперь: заголовочный файл - это то, что нужно «вставить» в несколько файлов CPP. Каждый файл CPP переводится независимо от любого другого. Компилятор, переводящий разные CPP-файлы, все получающие одно и то же определение, поместит « код создания » для определенного объекта во все результирующие OBJ.
Компилятор не знает (и никогда не узнает), будут ли когда-либо использоваться все эти OBJ для формирования единой программы или отдельно для формирования разных независимых программ.
Компоновщик не знает, как и почему существуют определения и откуда они берутся (он даже не знает о C ++: каждый «статический язык» может создавать определения и ссылки, которые должны быть связаны). Он просто знает, что есть ссылки на данный «символ», который «определен» по данному результирующему адресу.
Если для данного символа существует несколько определений (не путайте определения со ссылками), компоновщик не знает (не зависит от языка), что с ними делать.
Это похоже на объединение нескольких городов в один большой город: если у вас есть два « квадрата времени » и несколько людей, приходящих извне с просьбой перейти на « квадрат времени », вы не можете выбрать чисто техническую основу. (без каких-либо знаний о политике, которая назначила эти имена и будет отвечать за их управление) в каком именно месте их отправлять.
источник
Это необходимо, потому что в противном случае компилятор не знает, куда поместить переменную. Каждый файл cpp индивидуально компилируется и не знает о другом. Компоновщик разрешает переменные, функции и т. Д. Я лично не вижу, в чем разница между членами vtable и static (нам не нужно выбирать, в каком файле определяется vtable).
Я в основном предполагаю, что авторам компиляторов легче реализовать это таким образом. Статические переменные вне класса / структуры существуют и, возможно, либо по соображениям согласованности, либо потому, что авторам компиляторов было бы «легче реализовать», что они определили это ограничение в стандартах.
источник
Я думаю, что нашел причину. Определение
static
переменной в отдельном пространстве позволяет инициализировать ее любым значением. Если не инициализировано, то по умолчанию будет 0.До C ++ 11 инициализация в классе не была разрешена в C ++. Поэтому нельзя писать так:
Таким образом, теперь для инициализации переменной нужно написать ее вне класса как:
Как уже говорилось в других ответах,
int X::i
в настоящее время глобальный и объявление глобального во многих файлах вызывает ошибку ссылки на несколько символов.Таким образом, нужно объявить
static
переменную класса внутри отдельной единицы перевода. Тем не менее, все же можно утверждать, что следующий способ должен указывать компилятору не создавать несколько символовисточник
A :: x является просто глобальной переменной, но namespace'd для A, и с ограничениями доступа.
Кто-то еще должен объявить это, как и любую другую глобальную переменную, и это может даже быть сделано в проекте, который статически связан с проектом, содержащим остальную часть кода А.
Я бы назвал все это плохим дизайном, но есть несколько функций, которые вы можете использовать таким образом:
порядок вызова конструктора ... Не важно для int, но для более сложного члена, который может обращаться к другим статическим или глобальным переменным, это может быть критично.
статический инициализатор - вы можете позволить клиенту решить, к чему должен быть инициализирован A :: x.
в c ++ и c, поскольку у вас есть полный доступ к памяти через указатели, физическое расположение переменных является значительным. Есть очень плохие вещи, которые вы можете использовать в зависимости от того, где находится переменная в объекте ссылки.
Я сомневаюсь, что это «почему» возникла такая ситуация. Вероятно, это просто эволюция C, превращающаяся в C ++, и проблема обратной совместимости, которая мешает вам изменить язык сейчас.
источник