Espacios de nombres
Variantes
Acciones

Datos miembro no estáticos

De cppreference.com
< cpp‎ | language
 
 
 
 

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ático
    int& r;               // dato miembro no estático de tipo de referencia
    int 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áticos
    struct NestedS {
        std::string s;
    } d5;                 // dato miembro no estático de tipo anidado
    char 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 de 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 tipo C, aunque puede tener un dato miembro no estático de tipo C& (referencia a C) o C* (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 de clase que no son de unión, los miembros con el mismo acceso a miembro y 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 de 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 { const int b1; volatile char b2; }; 
// la secuencia inicial común de A y B  es A.a, A.b y B.b1, B.b2
struct C { int c; unsigned : 0; char b; };
// la secuencia inicial común de A y C es A.a y C.c
struct D { int d; char b : 4; };
// la secuencia inicial común de A y D es A.a y D.d
struct E { unsigned int e; char b; };
// la secuencia inicial común de A y E está vacía

Dos tipos de 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:

  • Si una unión de diseño estándar contiene dos (o más) clases de diseño estándar como miembros, y estas clases tienen una secuencia inicial común de datos miembro, está bien definido examinar a cualquier miembro de esa secuencia inicial común independientemente de qué miembro de la unión esté activo.
(hasta C++14)
  • En una unión de diseño estándar con un miembro activo de tipo de clase de no unión T1, se permite leer un dato miembro no estático m de otro miembro de unión de tipo de clase de no unión T2 siempre que m sea parte de la secuencia inicial común de T1 y T2 (excepto que leer un miembro volátil a través de un glvalue no volatil es indefinido).
(desde C++14)
  • Un puntero a un objeto de tipo de 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:

1) En la lista de inicializadores de miembros del constructor.
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.

#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

No se permiten los inicializadores de miembro por defecto para los miembros de campos de bits.

(hasta C++20)

Los miembros de tipo array no pueden deducir su tamaño de los inicializadores de miembros:

struct X {
   int a[] = {1,2,3}; // ERROR
   int 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 acuerdo
    const int& 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 acuerdo
    const 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:

1) Como parte de la expresión de acceso a miembro de clase, en la que la clase tiene este miembro o se deriva de una clase que tiene este miembro, incluidas las expresiones de acceso a miembro implícitas 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();
};
2) Para formar un puntero a miembro no estático.
struct S
{
   int m;
   void f();
};
int S::*p = &S::m;       // de acuerdo: uso de m para hacer un puntero a miembro
void (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;
   static const std::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

[editar] Véase también