Спецификаторы класса хранения
Спецификаторы класса хранения являются частью decl-specifier-seq синтаксиса объявления имени. Вместе со областью видимости имени они контролируют две независимые свойства имени: его срок существования и его связь.
| (до C++17) |
-
static- статический или поточный срок существования и внутренняя связь (или внешняя связь для статических членов класса, не находящихся в анонимном пространстве имён). -
extern- статический или поточный срок существования и внешняя связь.
| (с C++11) |
-
mutable- не влияет на срок существования или связь. Смотрите const/volatile для объяснения.
В объявлении может присутствовать только один спецификатор класса хранения за исключением того, что thread_local может быть объединён с static или с extern(с C++11).
Объяснение
| 1) Спецификатор auto разрешён только для объектов, объявленных в области блока или в списке параметров функции. Он указывает на автоматический срок существования, который является значением по умолчанию для этих видов объявлений. Значение этого ключевого слова изменилось в C++11. | (до C++11) |
| 2) Спецификатор register разрешён только для объектов, объявленных в области блока и в списке параметров функции. Он указывает на автоматический срок существования, который является значением по умолчанию для этих видов объявлений. Кроме того, наличие этого ключевого слова может использоваться как подсказка для оптимизатора, чтобы хранить значение этой переменной в регистре процессора. Это ключевое слово устарело. | (до C++17) |
static разрешён только в объявлениях объектов (кроме списка параметров функций), объявлениях функций (кроме области блока) и объявлениях анонимных объединений. При использовании в объявлении члена класса он объявляет статический член. При использовании в объявлении объекта он задаёт статический срок существования (кроме случая, когда сопровождается thread_local). При использовании в объявлении на уровне пространства имён он задаёт внутреннюю связь. extern разрешён только в объявлениях переменных и функций (кроме членов класса или параметров функций). Он задаёт внешнюю связь и технически не влияет на срок существования, но его нельзя использовать в определении объекта с автоматическим сроком существования, поэтому все extern объекты имеют статический или потоковый срок существования. Кроме того, объявление переменной, использующее extern и не имеющее инициализатора, не является определением.| 5) Ключевое слово thread_local разрешено только для объектов, объявленных на уровне пространства имён, объектов, объявленных в области блока, и статических данных-членов. Оно указывает, что объект имеет потоковый срок существования. Если thread_local является единственным спецификатором класса хранения, применяемым к переменной в области блока, также подразумевается static. Его можно объединить с static или extern, чтобы задать внутреннюю или внешнюю связь соответственно (за исключением статических данных-членов, у которых всегда внешняя связь). Смотрите Нелокальные переменные и Статические локальные переменные для получения подробностей об инициализации объектов с этим сроком существования. | (с C++11) |
Срок существования
Все объекты в программе имеют один из следующих сроков существования:
- автоматический срок существования. Память для объекта выделяется в начале содержащего блока кода и освобождается в конце. Все локальные объекты имеют такой срок существования, за исключением объявленных static, extern или thread_local.
- статический срок существования. Память для объекта выделяется при запуске программы и освобождается при завершении. Существует только один экземпляр объекта. Все объекты, объявленные на уровне пространства имён (включая глобальное пространство имён), имеют такой срок существования, а также те, которые объявлены с static или extern. Смотрите Нелокальные переменные и Статические локальные переменные для получения подробностей об инициализации объектов с этим сроком существования.
| (с C++11) |
- динамический срок существования. Память для объекта выделяется и освобождается по запросу с помощью функций динамического выделения памяти. Смотрите new-выражение для получения подробностей об инициализации объектов с этим сроком существования.
Срок существования подобъектов и членов-ссылок совпадает со сроком существования их родительского объекта.
Связь
Имя, обозначающее объект, ссылку, функцию, тип, шаблон, пространство имён или значение, может иметь связь. Если имя имеет связь, оно ссылается на ту же сущность, что и то же имя, введённое в другом объявление в другом объявление. Если переменная, функция или другая сущность с тем же именем объявлены в нескольких областях видимости, но не имеют достаточной связи, то создаются несколько экземпляров сущности.
Признаются следующие виды связей:
Отсутствие связи
К имени можно обратиться только из области видимости, в которой оно объявлено.
Любое из следующих имен, объявленных в области блока, не имеет связи:
- переменные, которые явно не объявлены extern (независимо от модификатора static);
- локальные классы и их члены-функции;
- другие имена, объявленные в области блока, такие как typedef, перечисления и перечислители.
Имена, не указанные с внешней, модульной,(с C++20) или внутренней связью, также не имеют связи, независимо от того, в какой области видимости они объявлены.
Внутренняя связь
К имени можно обратиться из всех областей видимости в текущем объекте трансляции.
Любое из следующих имён, объявленных на уровне пространства имён, имеет внутреннюю связь:
- переменные, шаблонные переменные(с C++14), функции или шаблоны функций, объявленные static;
- нешаблонные (с C++14)переменные неподписанного константного типа, за исключением
| (с C++17) |
| (с C++20) |
- они явно объявлены extern, или
- они были объявлены ранее, и предыдущее объявление не имело внутренней связи;
- члены данных анонимных объединений.
| Кроме того, все имена, объявленные в безымянном пространстве имён или в пространстве имён внутри безымянного пространства имён, даже если они явно объявлены extern, имеют внутреннюю связь. | (с C++11) |
Внешняя связь
К имени можно обратиться из областей видимости в других единицах трансляции. Переменные и функции с внешней связью также имеют языковую связь, что позволяет связывать единицы трансляции, написанные на разных языках программирования.
Любое из следующих имён, объявленных на уровне пространства имён, имеет внешнюю связь, за исключением случаев объявления в безымянном пространстве имён или если их объявления привязаны к именованному модулю и не экспортируются(с C++20):
- переменные и функции, не перечисленные выше (то есть, функции, не объявленные static, не-const переменные, не объявленные static, и любые переменные, объявленные extern);
- перечисления;
- имена классов, их методов-членов, статических данных-членов (const или нет), вложенных классов и перечислений, а также функций, впервые объявленных с помощью friend в телах классов;
- имена всех шаблонов, не перечисленных выше (то есть, не шаблонов функций, объявленных static).
Любые из следующих имён, объявленные в области видимости блока, имеют внешний связующий компонент:
- имена переменных, объявленных extern;
- имена функций.
Связь модуляК имени можно обратиться только из областей видимости в том же модуле или в других единицах трансляции одного и того же модуля с заданным именем. Имена, объявленные в области видимости пространства имён, имеют связь модуля, если их объявления прикреплены к именованному модулю и не экспортируются, и не имеют внутренней связи. | (с C++20) |
Статические локальные переменные
Переменные, объявленные в области видимости блока со спецификатором static или thread_local(с C++11) имеют статическую или потоковую(с C++11) продолжительность хранения, но инициализируются в первый раз, когда управление проходит через их объявление (если их инициализация не является нулевой или постоянной, которая может быть выполнена до первого входа в блок). При последующих вызовах объявление пропускается.
Если инициализация выбрасывает исключение, переменная не считается инициализированной, и инициализация будет повторно попытана в следующий раз, когда управление пройдёт через объявление.
Если инициализация рекурсивно входит в блок, в котором переменная инициализируется, поведение неопределённо.
| Если несколько потоков пытаются одновременно инициализировать одну и ту же статическую локальную переменную, инициализация происходит ровно один раз (аналогичное поведение может быть получено для произвольных функций с Примечание: обычные реализации этой функции используют варианты шаблона двойной проверки блокировки, что уменьшает время выполнения для уже инициализированных локальных статик до одного неатомарного сравнения булевых значений. | (с C++11) |
Деструктор для статической переменной в области видимости блока вызывается при выходе программы, но только если инициализация прошла успешно.
Статические объекты в локальной функции во всех определениях одной и той же встроенной функции (которая может быть неявной встроенной) все ссылаются на один и тот же объект, определённый в одной единице трансляции, при условии, что функция имеет внешний связующий компонент.
Локальные сущности единицы трансляции
Концепция локальных сущностей единицы трансляции стандартизована в C++20, см. эту страницу для получения более подробной информации.
Сущность является локальной для единицы трансляции (или локальной для ТУ) если
- она имеет имя с внутренней связью, или
- она не имеет связи с именем и вводится в определении сущности локальной для ТУ, или
- она является шаблоном или специализацией шаблона, аргумент шаблона или объявление шаблона которого используют сущность локальную для ТУ.
Плохие вещи (обычно нарушение ODR) могут произойти, если тип сущности, не являющейся локальной для ТУ, зависит от сущности, локальной для ТУ, или если объявление, или руководство по выводу для,(с C++17) сущности, не являющейся локальной для ТУ, называет сущность, локальную для ТУ, за пределами её
- тела функции для нестатической функции или шаблона функции
- инициализатора переменной или шаблона переменной
- объявлений friend в определении класса
- использования значения переменной, если переменная может быть использована в константных выражениях
| Такие использования запрещены в модуле интерфейсной единице (за пределами его частной части модуля, если таковая имеется) или модульном разделе, и устарели в любом другом контексте. Объявление, которое появляется в одной единице трансляции, не может называть сущность, локальную для ТУ, объявленную в другой единице трансляции, которая не является единицей заголовка. Объявление, сгенерированное для шаблона, появляется в момент специализации шаблона. | (с C++20) |
Примечания
Имена в области видимости пространства имён верхнего уровня (область видимости файла в C), которые являются const и не extern, имеют внешнюю связь в C, но внутреннюю связь в C++.
С C++11, auto больше не является спецификатором класса хранения; он используется для указания вывода типа.
| В C, адрес переменной register не может быть взят, но в C++, переменная, объявленная как register, семантически неотличима от переменной, объявленной без каких-либо спецификаторов класса хранения. | (до C++17) |
| В C++, в отличие от C, переменные не могут быть объявлены как register. | (с C++17) |
Имена переменных thread_local с внутренней или внешней связью, на которые ссылаются разные области видимости, могут ссылаться на один и тот же или на разные экземпляры в зависимости от того, выполняется ли код в одном или разных потоках.
Ключевое слово extern также может быть использовано для указания связи языка и явно объявленных шаблонов классов, но в этих случаях это не спецификатор класса хранения (за исключением случаев, когда объявление непосредственно содержится в спецификации связи языка, в этом случае объявление рассматривается так, как будто оно содержит спецификатор extern).
Ключевое слово mutable является спецификатором класса хранения в грамматике языка C++, хотя оно не влияет на продолжительность хранения или связь.
Спецификаторы класса хранения, за исключением thread_local, не допускаются для явных специализаций и явных инстанциаций:
template<class T>
struct S
{
thread_local static int tlm;
};
template<>
thread_local int S<float>::tlm = 0; // "static" does not appear here| Макросы проверки функций | Значение | Std | Функция |
|---|---|---|---|
__cpp_threadsafe_static_init | 200806L | (C++11) | Динамическая инициализация и уничтожение с одновременностью |
Ключевые слова
auto, register, static, extern, thread_local, mutable
Пример
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
thread_local unsigned int rage = 1;
std::mutex cout_mutex;
void increase_rage(const std::string& thread_name)
{
++rage; // modifying outside a lock is okay; this is a thread-local variable
std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "Rage counter for " << thread_name << ": " << rage << '\n';
}
int main()
{
std::thread a(increase_rage, "a"), b(increase_rage, "b");
{
std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "Rage counter for main: " << rage << '\n';
}
a.join();
b.join();
}Возможный вывод:
Rage counter for a: 2 Rage counter for main: 1 Rage counter for b: 2
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применимо к | Поведение, опубликованное | Правильное поведение |
|---|---|---|---|
| CWG 216 | C++98 | безымянный класс и перечисление в области класса имеют разную область видимости, чем те, что в области именованного пространства | все они имеют внешнюю область видимости в этих областях |
| CWG 389 | C++98 | имя без области видимости не должно использоваться для объявления сущности с областью видимости | тип без области видимости не должен использоваться в качестве типа переменной или функции с областью видимости, если переменная или функция не имеют области видимости C |
| CWG 426 | C++98 | сущность могла быть объявлена с внутренней и внешней областью видимости в одном и том же блоке трансляции | в этом случае программа является некорректной |
| CWG 527 | C++98 | ограничение типа, введенное решением CWG 389, также применялось к переменным и функциям, которые не могут быть названы за пределами своих блоков трансляции | это ограничение снимается для этих переменных и функций (т.е. без области видимости или внутренней области видимости, или объявленных в безымянных именованных пространствах) |
| CWG 809 | C++98 | register выполнял очень мало функций | устаревший |
| CWG 1648 | C++11 |
static подразумевалось даже если thread_local комбинируется с extern | подразумевается только если другой модификатор хранения не присутствует |
| CWG 1686 | C++98 C++11 | имя нестатической переменной, объявленной в области именованного пространства, имело внутреннюю область видимости только если оно явно объявлено const (C++98) или constexpr (C++11) | требовался только тип для квалификации |
| CWG 2019 | C++98 | длительность хранения ссылки членов была неопределенной | такая же, как у их полного объекта |
| CWG 2387 | C++14 | неясно, имеют ли константные переменные шаблоны внутреннюю область видимости по умолчанию | квалификатор const не влияет на область видимости переменных шаблонов или их экземпляров |
| P2788R0 | C++20 | объявление константной переменной в области именованного пространства придавало ей внутреннюю область видимости даже в блоке модуля | внутренняя область видимости не предоставляется |
См. также
| Документация C для длительности хранения |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/storage_duration