Datos miembro no estáticos
Los datos miembro no estáticos se declaran en una especificación de miembro de una clase.
class S {int n;// dato miembro no estáticoint& r;// dato miembro no estático de tipo referenciaint a[2]={1, 2};// dato miembro no estático con inicializador de miembro por defecto (C++11)std::string s, *ps;// dos datos miembro no estáticosstruct NestedS {std::string s;} d5;// dato miembro no estático de tipo anidadochar bit :2;// campo de bits de dos bits};
Se permite cualquier declaración simple, excepto
- no se permiten los especificadores de clase de almacenamiento extern y register;
- no se permite el especificador de clase de almacenamiento thread_local (pero se permite para datos miembro estáticos);
- no se permiten los tipos incompletos, tipos clase abstractas, y arrays de los mismos no se permiten: en particular, una clase
C
no puede tener un dato miembro no estático de tipoC
, aunque puede tener un dato miembro no estático de tipoC&
(referencia a C) oC*
(puntero a C); - un dato miembro no estático no puede tener el mismo nombre que el nombre de la clase si al menos un constructor declarado por el usuario está presente;
- el especificador auto no puede usarse en una declaración de dato miembro no estático (aunque se permite para datos miembro estáticos que son inicializados en la definición de la clase).
Además, se permiten declaraciones de campos de bits.
Contenido |
[editar]Diseño
Cuando se crea un objeto de alguna clase C
, cada dato miembro no estático de tipo no referencia se asigna en alguna parte de la representación del objeto de C
. Si los miembros de referencia ocupan algún almacenamiento está definido por la implementación, pero su duración de almacenamiento es la misma que la del objeto del que son miembros.
Para los tipos clase no-unión, los miembros con el mismo acceso a miembroy con tamaño distinto de cero(desde C++20) siempre se asignan para que los miembros declarados más tarde tengan direcciones más altas dentro de un objeto de clase. Los miembros con diferente control de acceso se asignan en un orden no especificado (el compilador puede agruparlos). Los requisitos de alineación pueden requerir relleno entre los miembros o después del último miembro de una clase.
[editar]Diseño estándar
Una clase donde todos los datos miembro no estáticos tienen el mismo control de acceso y se cumplen ciertas otras condiciones se conoce como tipo de diseño estándar (véase StandardLayoutType para ver la lista de requisitos).
Dos tipos clase que no son uniones, de diseño estándar, pueden tener una secuencia inicial común de datos miembro no estáticos y campos de bits(desde C++14), para una secuencia de uno o más miembros iniciales (en el orden de declaración), si los miembros tienen tipos de diseño compatibles ya sea que ambos estén declarados con el atributo [[no_unique_address]]
o declarados sin el atributo(desde C++20) y si son campos de bits, tienen el mismo ancho(desde C++14).
struct A {int a;char b;};struct B {constint b1;volatilechar b2;};// la secuencia inicial común de A y B es A.a, A.b y B.b1, B.b2struct C {int c;unsigned:0;char b;};// la secuencia inicial común de A y C es A.a y C.cstruct D {int d;char b :4;};// la secuencia inicial común de A y D es A.a y D.dstruct E {unsignedint e;char b;};// la secuencia inicial común de A y E está vacía
Dos tipos clase de que no son uniones se dice que son de diseño compatible si son del mismo tipo ignorando los calificadores-cv, si es que los hay(desde C++14), son enumeraciones de diseño compatible (p. ej., enumeraciones con el mismo tipo subyacente), o si su secuencia inicial común consiste en que cada dato miembro no estático y campo de bits(desde C++14) (en el ejemplo anterior, A
y B
sea de diseño compatible)
Dos uniones de diseño estándar son llamados de diseño compatible si tienen el mismo número de datos miembro no estáticos y los datos miembro no estáticos correspondientes (en cualquier orden) tienen tipos de diseño compatible.
Los tipos de diseño estándar tienen las siguientes propiedades especiales:
| (hasta C++14) |
| (desde C++14) |
- Un puntero a un objeto de tipo clase de diseño estándar puede ser convertido mediante reinterpret_cast para apuntar a su primer dato miembro no estático, no de campo de bits (si tiene datos miembro no estáticos), o a cualquiera de sus subobjetos de clases base (si tiene alguno), y viceversa. En otras palabras, el relleno no está permitido antes del primer dato miembro de un tipo de diseño estándar. Observa que las reglas para alias de tipo todavía se aplican al resultado de tal conversión.
- La macro offsetof puede usarse para determinar el desplazamiento de cualquier miembro desde el comienzo de una clase de diseño estándar.
[editar]Inicialización de miembros
Los datos miembro no estáticos pueden inicializarse de dos maneras:
struct S {int n;std::string s; S(): n(7)// inicialización directa de n, inicialización por defecto de s{}};
2) Mediante un inicializador de miembro por defecto, que es un inicializador entre llaves o con el signo igual incluido en la declaración de miembro y que se usa si el miembro se omite de la lista de inicializadores de miembros de un constructor. struct S {int n =7;std::string s{'a', 'b', 'c'}; S()// inicializador de miembro por defecto// usará inicialización de copia para n,// inicialización de lista para s{}}; Si un miembro tiene un inicializador de miembro por defecto y también aparece en la lista de inicializadores de miembros, el inicializador de miembro por defecto se ignora para ese constructor. Ejecuta este código #include <iostream> int x =0;struct S {int n =++x; S(){}// usa inicializador de miembro por defecto S(int arg): n(arg){}// usa el inicializador de miembro }; int main(){std::cout<< x <<'\n';// imprime 0 S s1;std::cout<< x <<'\n';// imprime 1 (se ejecutó el inicializador de miembro por defecto) S s2(7);std::cout<< x <<'\n';// imprime 1 (se ejecutó el inicializador de miembro por defecto)} Salida: 0 1 1
Los miembros de tipo array no pueden deducir su tamaño de los inicializadores de miembros: struct X {int a[]={1,2,3};// ERRORint b[3]={1,2,3};// de acuerdo}; No se permite que los inicializadores de miembro por defecto ocasionen la definición implícita o por defecto de un constructor por defecto para la clase adjunta o la especificación de excepción de ese constructor. : struct nodo { nodo* p = new nodo;// ERROR: uso de node::node() implícito o por defecto}; | (desde C++11) | ||
Los miembros de referencia no pueden vincularse a temporales en un inicializador de miembro por defecto (nota: la misma regla existe para las listas de inicialización de miembros). struct A { A()=default;// de acuerdo A(int v): v(v){}// de acuerdoconstint& v =42;// de acuerdo}; A a1;// ERROR: vincular una referencia a un temporal está mal formado A a2(1);// de acuerdo (se ignora el inicializador de miembro por defecto // porque v appears in a constructor) sin embargo, a2.v es una// referencia pendiente Es un error si un inicializador de miembro por defecto tiene una subexpresión que ejecutaría una inicialización de agregado que usaría el mismo inicializador: struct A;extern A a;struct A {const A& a1{ A{a, a}};// de acuerdoconst A& a2{ A{}};// ERROR}; A a{a, a};// de acuerdo | (desde C++14) |
[editar]Uso
El nombre de un dato miembro no estático o una función miembro no estática solo puede aparecer en las siguientes tres situaciones:
this->
que aparecen cuando un nombre de miembro no estático se usa en cualquiera de los contextos donde this está permitido (dentro de los cuerpos de funciones miembro, en las listas de inicialización de miembros, en los inicializadores de miembro por defecto dentro de la clase). struct S {int m;int n;int x = m;// de acuerdo: se permite this-> implícito en inicializadores por defecto (C++11) S(int i): m(i), n(m)// de acuerdo: se permite this-> implícito en las listas de inicialización de miembros{ this->f();// expersión de acceso a miembro explícita f();// se permite this-> explícito en los cuerpos de las funciones miembro}void f();};
struct S {int m;void f();};int S::*p =&S::m;// de acuerdo: uso de m para hacer un puntero a miembrovoid(S::*fp)()=&S::f;// de acuerdo: use of f para hacer un puntero a miembro
3) (para datos miembro solamente, no funciones miembro) Cuando es usado en operandos no evaluados. struct S {int m;staticconststd::size_t sz = sizeof m;// de acuerdo: m en un operando no evaluado};std::size_t j = sizeof(S::m+42);// de acuerdo: aun cuando no hay un objeto "this" para m | (desde C++03) |
[editar]Reportes de defectos
Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.
ID | Aplicado a | Comportamiento según lo publicado | Comportamiento correcto |
---|---|---|---|
CWG 613 | C++03 | Usos no evaluados de datos miembro no estáticos no se permiten | Tales casos se permiten |
CWG 1696 | C++14 | Miembros de referencia podían inicializarse a temporales (cuya duración terminaría al final del constructor) | Tal inicialización está mal formada |
CWG 1397 | C++11 | La clase se consideraba como completa en los inicializadores de miembro por defecto | La inicialización de miembro por defecto no puede generar una definición del constructor por defecto |
CWG 1719 | C++14 | Tipos calificados-cv de manera diferente no eran de diseño compatible | Se ignoran los calificadores-cv, especificación mejorada |
CWG 2254 | C++14 | Puntero a clase con diseño estándar sin datos miembro puede ser convertida mediante reinterpret_cast a su primer clase base | Puede ser convertida mediante reinterpret_cast a cualquiera de sus clases base |