C++
Especificadores de clase de almacenamiento
Buscar..
Introducción
Los especificadores de clase de almacenamiento son palabras clave que se pueden usar en declaraciones. No afectan el tipo de la declaración, pero generalmente modifican la forma en que se almacena la entidad.
Observaciones
Hay seis especificadores de clase de almacenamiento, aunque no todos en la misma versión del idioma: auto
(hasta C ++ 11), register
(hasta C ++ 17), static
, thread_local
(desde C ++ 11), extern
y mutable
Según la norma,
A lo sumo, un especificador de clase de almacenamiento aparecerá en un decl-specifier-seq dado , excepto que
thread_local
puede aparecer constatic
oextern
.
Una declaración no puede contener ningún especificador de clase de almacenamiento. En ese caso, el idioma especifica un comportamiento predeterminado. Por ejemplo, de forma predeterminada, una variable declarada en el ámbito del bloque tiene implícitamente una duración de almacenamiento automático.
mudable
Un especificador que se puede aplicar a la declaración de un miembro de datos no estático y no de referencia de una clase. Un miembro mutable de una clase no es const
incluso cuando el objeto es 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;
}
};
Un segundo significado para mutable
se añadió en C ++ 11. Cuando sigue la lista de parámetros de un lambda, suprime la const
implícita en el operador de llamada de función del lambda. Por lo tanto, un lambda mutable puede modificar los valores de las entidades capturadas por copia. Ver lambdas mutable para más detalles.
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;
}
Tenga en cuenta que mutable
no es un especificador de clase de almacenamiento cuando se utiliza de esta manera para formar un lambda mutable.
registro
Un especificador de clase de almacenamiento que insinúa al compilador que una variable será muy utilizada. La palabra "registro" está relacionada con el hecho de que un compilador puede elegir almacenar una variable de este tipo en un registro de CPU para que se pueda acceder a ella en menos ciclos de reloj. Fue desaprobado a partir de C ++ 11.
register int i = 0;
while (i < 100) {
f(i);
int g = i*i;
i += h(i, g);
}
Tanto las variables locales como los parámetros de función pueden ser declarados de register
. A diferencia de C, C ++ no impone restricciones a lo que se puede hacer con una variable de register
. Por ejemplo, es válido tomar la dirección de una variable de register
, pero esto puede impedir que el compilador realmente almacene dicha variable en un registro.
El register
palabras clave está sin uso y reservado. Un programa que utiliza el register
palabras clave está mal formado.
estático
El especificador de clase de almacenamiento static
tiene tres significados diferentes.
Da enlace interno a una variable o función declarada en el ámbito del espacio de nombres.
// 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)); }
Declara que una variable tiene una duración de almacenamiento estática (a menos que sea
thread_local
). Las variables del ámbito del espacio de nombres son implícitamente estáticas. Una variable local estática se inicializa solo una vez, el primer control de tiempo pasa por su definición y no se destruye cada vez que se sale de su alcance.void f() { static int count = 0; std::cout << "f has been called " << ++count << " times so far\n"; }
Cuando se aplica a la declaración de un miembro de la clase, declara que ese miembro es un miembro estático .
struct S { static S* create() { return new S; } }; int main() { S* s = S::create(); }
Tenga en cuenta que en el caso de un miembro de datos estáticos de una clase, tanto 2 como 3 se aplican simultáneamente: la palabra clave static
convierte al miembro en un miembro de datos estáticos y lo convierte en una variable con una duración de almacenamiento estática.
auto
Declara una variable que tiene duración de almacenamiento automático. Es redundante, ya que la duración del almacenamiento automático ya es la predeterminada en el ámbito del bloque, y el especificador automático no está permitido en el ámbito del espacio de nombres.
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
En C ++ 11, el significado de auto
cambió completamente, y ya no es un especificador de clase de almacenamiento, sino que se usa para la deducción de tipos .
externo
El especificador de clase de almacenamiento extern
puede modificar una declaración de una de las tres formas siguientes, según el contexto:
Se puede utilizar para declarar una variable sin definirla. Por lo general, esto se usa en un archivo de encabezado para una variable que se definirá en un archivo de implementación separado.
// 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)
constexpr
un enlace externo a una variable en el ámbito del espacio de nombres, incluso siconst
oconstexpr
hubieran provocado que tuviera un enlace interno.// 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 }
Redecierra una variable en el ámbito del bloque si se declaró previamente con un enlace. De lo contrario, declara una nueva variable con vinculación, que es un miembro del espacio de nombres envolvente más cercano.
// 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 }
Una función también puede ser declarada extern
, pero esto no tiene efecto. Generalmente se usa como una sugerencia para el lector de que una función declarada aquí se define en otra unidad de traducción. Por ejemplo:
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
En el código anterior, si f
se cambiara a extern
y g
a no extern
, no afectaría en absoluto la corrección o la semántica del programa, pero probablemente confundiría al lector del código.