C++
Спецификаторы класса хранения
Поиск…
Вступление
Спецификаторы класса хранения - это ключевые слова, которые могут использоваться в объявлениях. Они не влияют на тип объявления, но обычно изменяют способ хранения объекта.
замечания
Существует шесть спецификаций класса хранения, хотя и не все в одной и той же версии языка: auto
(до C ++ 11), register
(до C ++ 17), static
, thread_local
(начиная с C ++ 11), extern
и mutable
.
Согласно стандарту,
Не более одного спецификатора класса хранения должно появляться в заданном spec-spec-seq, за исключением того, что
thread_local
может отображаться соstatic
илиextern
.
Объявление может содержать спецификатор класса хранения. В этом случае язык определяет поведение по умолчанию. Например, по умолчанию переменная, объявленная в области блока, неявно имеет автоматическую продолжительность хранения.
изменчивый
Спецификатор, который может быть применен к объявлению нестатического, не ссылочного элемента данных класса. Изменчивый член класса не const
даже если объект const
.
class C {
int x;
mutable int times_accessed;
public:
C(): x(0), times_accessed(0) {
}
int get_x() const {
++times_accessed; // ok: const member function can modify mutable data member
return x;
}
void set_x(int x) {
++times_accessed;
this->x = x;
}
};
Второе значение для mutable
было добавлено в C ++ 11. Когда он следует за списком параметров лямбда, он подавляет неявный const
в операторе вызова функции лямбда. Следовательно, изменяемая лямбда может изменять значения объектов, захваченных копией. См. Изменчивые лямбды для более подробной информации.
std::vector<int> my_iota(int start, int count) {
std::vector<int> result(count);
std::generate(result.begin(), result.end(),
[start]() mutable { return start++; });
return result;
}
Обратите внимание, что mutable
не является спецификатором класса хранения, когда используется таким образом, чтобы сформировать изменяемую лямбду.
регистр
Спецификатор класса хранения, который подсказывает компилятору, что переменная будет сильно использоваться. Слово «регистр» связано с тем, что компилятор может захотеть сохранить такую переменную в регистре CPU, чтобы к ней можно было получить доступ за меньшее количество тактов. Он устарел, начиная с C ++ 11.
register int i = 0;
while (i < 100) {
f(i);
int g = i*i;
i += h(i, g);
}
Локальные переменные и параметры функции могут быть объявлены register
. В отличие от C, C ++ не устанавливает никаких ограничений на то, что можно сделать с помощью переменной register
. Например, можно принять адрес переменной register
, но это может помешать компилятору фактически сохранить такую переменную в регистре.
register
ключевых слов не используется и сохраняется. Программа, использующая register
ключевых слов, плохо сформирована.
статический
Спецификатор класса static
хранилища имеет три разных значения.
Дает внутреннюю связь с переменной или функцией, объявленной в области пространства имен.
// internal function; can't be linked to static double semiperimeter(double a, double b, double c) { return (a + b + c)/2.0; } // exported to client double area(double a, double b, double c) { const double s = semiperimeter(a, b, c); return sqrt(s*(s-a)*(s-b)*(s-c)); }
Объявляет переменную для статической продолжительности хранения (если она не является
thread_local
). Переменные пространства-пространства неявно статичны. Статическая локальная переменная инициализируется только один раз, первый контроль времени проходит через ее определение и не уничтожается при каждом выходе из нее.void f() { static int count = 0; std::cout << "f has been called " << ++count << " times so far\n"; }
При применении к объявлению члена класса объявляет этот элемент статическим членом .
struct S { static S* create() { return new S; } }; int main() { S* s = S::create(); }
Обратите внимание, что в случае статического члена данных класса оба одновременно применяются как 2, так и 3: static
ключевое слово делает его членом в статическом элементе данных и превращает его в переменную со статической продолжительностью хранения.
авто
Объявляет переменную для автоматического хранения. Это избыточно, поскольку автоматическая длительность хранения уже является значением по умолчанию в области блока, а авто-спецификатор не разрешен в области пространства имен.
void f() {
auto int x; // equivalent to: int x;
auto y; // illegal in C++; legal in C89
}
auto int z; // illegal: namespace-scope variable cannot be automatic
В C ++ 11 auto
изменяется смысл и больше не является спецификатором класса хранения, а вместо этого используется для вывода типа .
внешний
Спецификатор класса extern
хранилища может изменять объявление одним из трех следующих способов, в зависимости от контекста:
Его можно использовать для объявления переменной без ее определения. Как правило, это используется в файле заголовка для переменной, которая будет определена в отдельном файле реализации.
// global scope int x; // definition; x will be default-initialized extern int y; // declaration; y is defined elsewhere, most likely another TU extern int z = 42; // definition; "extern" has no effect here (compiler may warn)
Это дает внешнюю связь с переменной в пространстве пространства имен, даже если
const
илиconstexpr
иначе вызвали бы ее внутреннюю связь.// global scope const int w = 42; // internal linkage in C++; external linkage in C static const int x = 42; // internal linkage in both C++ and C extern const int y = 42; // external linkage in both C++ and C namespace { extern const int z = 42; // however, this has internal linkage since // it's in an unnamed namespace }
Он переопределяет переменную в области блока, если она была ранее объявлена с помощью связи. В противном случае он объявляет новую переменную с привязкой, которая является членом ближайшего охватывающего пространства имен.
// global scope namespace { int x = 1; struct C { int x = 2; void f() { extern int x; // redeclares namespace-scope x std::cout << x << '\n'; // therefore, this prints 1, not 2 } }; } void g() { extern int y; // y has external linkage; refers to global y defined elsewhere }
Функция также может быть объявлена extern
, но это не имеет никакого эффекта. Он обычно используется как подсказка для читателя о том, что заявленная здесь функция определена в другой единицы перевода. Например:
void f(); // typically a forward declaration; f defined later in this TU
extern void g(); // typically not a forward declaration; g defined in another TU
В приведенном выше коде, если f
был изменен на extern
и g
на non extern
, это никак не повлияет на правильность или семантику программы, но скорее всего путает читателя кода.