Object
Los programas de C++ crean, destruyen, refieren, acceden y manipulan objetos .
Un objeto, en C++, tiene
-
tamaño (se puede determinar con
sizeof); -
requisito de alineación (se puede determinar con
alignof); - duración de almacenamiento (automática, estática, dinámica, local-de-hilo);
- tiempo de vida (limitado por la duración de almacenamiento o temporal);
- tipo ;
- valor (que puede ser indeterminado, p.ej. para tipos no-clase inicializados por defecto );
- opcionalmente, un nombre .
Las siguientes entidades no son objetos: valor, referencia, función, enumerador, tipo, miembro no estático de clase, plantilla, especialización de plantilla de clase o función, espacio de nombres, paquete de parámetros, y this .
Una variable es un objeto o una referencia que no es un miembro de datos no estático, que se introduce mediante una declaración .
Contenidos |
Creación de objetos
Los objetos pueden ser creados explícitamente mediante definiciones , new expresiones , throw expresiones , cambiando el miembro activo de una union y evaluando expresiones que requieren objetos temporales . El objeto creado está únicamente definido en la creación explícita de objetos.
Los objetos de tipos de duración implícita también pueden ser creados implícitamente mediante
- excepto durante la evaluación constante, operaciones que inician el tiempo de vida de un arreglo de tipo unsigned char o std::byte (desde C++17) , en cuyo caso tales objetos son creados en el arreglo,
- llamada a las siguientes funciones de asignación, en cuyo caso tales objetos son creados en el almacenamiento asignado:
-
- operator new (excepto durante la evaluación constante)
- operator new[] (excepto durante la evaluación constante)
- std::malloc
- std::calloc
- std::realloc
| (desde C++17) |
- llamada a las siguientes funciones de copia de representación de objetos , en cuyo caso dichos objetos se crean en la región de almacenamiento de destino o el resultado:
| (desde C++20) |
|
(desde C++23) |
Cero o más objetos pueden ser creados en la misma región de almacenamiento, siempre que hacerlo le dé al programa un comportamiento definido. Si dicha creación es imposible, por ejemplo, debido a operaciones conflictivas, el comportamiento del programa es indefinido. Si múltiples conjuntos de objetos creados implícitamente le dieran al programa un comportamiento definido, no está especificado cuál de dichos conjuntos de objetos es creado. En otras palabras, no se requiere que los objetos creados implícitamente estén definidos de manera única.
Después de crear objetos implícitamente dentro de una región específica de almacenamiento, algunas operaciones producen un puntero a un objeto creado adecuado . El objeto creado adecuado tiene la misma dirección que la región de almacenamiento. Asimismo, el comportamiento es indefinido solo si ningún valor de puntero puede dar al programa un comportamiento definido, y no se especifica qué valor de puntero se produce si hay múltiples valores que dan al programa un comportamiento definido.
#include <cstdlib> struct X { int a, b; }; X* MakeX() { // Uno de los comportamientos definidos posibles: // la llamada a std::malloc crea implícitamente un objeto de tipo X // y sus subobjetos a y b, y devuelve un puntero a ese objeto X X* p = static_cast<X*>(std::malloc(sizeof(X))); p->a = 1; p->b = 2; return p; }
Llamada a std::allocator::allocate o las funciones miembro especiales de copia/movimiento definidas implícitamente de tipos union también pueden crear objetos.
Representación de objeto y representación de valor
Algunos tipos y objetos tienen representaciones de objeto y representaciones de valor , se definen en la tabla siguiente:
| Entidad | Representación del objeto | Representación del valor |
|---|---|---|
un tipo de objeto completo
T
|
la secuencia de
N
unsigned
char
ocupada por un objeto completo de tipo
T
que no es un
campo de bits
, donde
N
es
sizeof
(
T
)
|
el conjunto de bits en la representación del objeto de
T
que participan en representar un valor de tipo
T
|
un objeto completo no-campo-de-bits
obj
de tipo
T
|
los bytes de
obj
correspondientes a la representación del objeto de
T
|
los bits de
obj
correspondientes a la representación del valor de
T
|
| un objeto de campo de bits bf | la secuencia de N bits ocupada por bf , donde N es el ancho del campo de bits | el conjunto de bits en la representación del objeto de bf que participan en representar el valor de bf |
Los bits en la representación del objeto de un tipo u objeto que no forman parte de la representación del valor son bits de relleno .
Para los tipos TriviallyCopyable , la representación del valor es parte de la representación del objeto, lo que significa que copiar los bytes ocupados por el objeto en el almacenamiento es suficiente para producir otro objeto con el mismo valor (excepto si el objeto es un subobjeto potencialmente superpuesto, o el valor es una representación trampa de su tipo y cargarlo en la CPU genera una excepción de hardware, como los valores de punto flotante SNaN ("signalling not-a-number") o los enteros NaT ("not-a-thing")).
Aunque la mayoría de las implementaciones no permiten representaciones trampa, bits de relleno o múltiples representaciones para tipos enteros, hay excepciones; por ejemplo un valor de un tipo entero en Itanium puede ser una representación trampa .
Lo contrario no es necesariamente cierto: dos objetos de un tipo TriviallyCopyable con diferentes representaciones de objeto pueden representar el mismo valor. Por ejemplo, múltiples patrones de bits de punto flotante representan el mismo valor especial NaN . Más comúnmente, se pueden introducir bits de relleno para satisfacer requisitos de alineación , tamaños de campo de bits , etc.
#include <cassert> struct S { char c; // valor de 1 byte // 3 bytes de bits de relleno (asumiendo alignof(float) == 4) float f; // valor de 4 bytes (asumiendo sizeof(float) == 4) bool operator==(const S& arg) const // igualdad basada en valor { return c == arg.c && f == arg.f; } }; void f() { assert(sizeof(S) == 8); S s1 = {'a', 3.14}; S s2 = s1; reinterpret_cast<unsigned char*>(&s1)[2] = 'b'; // modificar algunos bits de relleno assert(s1 == s2); // el valor no cambió }
Para los objetos de tipo char , signed char , y unsigned char (a menos que sean campos de bits de tamaño excesivo), cada bit de la representación del objeto debe participar en la representación del valor y cada posible patrón de bits representa un valor distinto (no se permiten bits de relleno, bits trampa ni representaciones múltiples).
Subobjetos
Un objeto puede tener subobjetos . Estos incluyen
- objetos miembro
- subobjetos de clase base
- elementos de array
Un objeto que no es un subobjeto de otro objeto se denomina objeto completo .
Si un objeto completo, un subobjeto miembro o un elemento de arreglo es de class type , su tipo se considera la clase más derivada , para distinguirlo del tipo de clase de cualquier subobjeto de clase base. Un objeto de tipo de clase más derivada o de tipo no-clase se denomina objeto más derivado .
Para una clase,
- sus data members no estáticos,
- sus base classes directas no virtuales, y,
- si la clase no es abstract , sus virtual base classes
se denominan sus subobjetos potencialmente construidos .
Tamaño
Un subobjeto es un
subobjeto potencialmente superpuesto
si es un subobjeto de clase base
o un miembro de datos no estático declarado con el atributo
[[
no_unique_address
]]
(desde C++20)
.
Un objeto obj solo puede posiblemente tener tamaño cero si se cumplen todas las siguientes condiciones:
- obj es un subobjeto potencialmente superpuesto.
- obj es de un tipo de clase sin funciones miembro virtuales y clases base virtuales.
- obj no tiene ningún subobjeto de tamaño distinto de cero ni campos de bits sin nombre de longitud distinta de cero.
Para un objeto obj que satisface todas las condiciones anteriores:
- Si obj es un subobjeto de clase base de un tipo de clase standard-layout (since C++11) sin miembros de datos no estáticos, tiene tamaño cero.
- En caso contrario, está definido por la implementación bajo qué circunstancias obj tiene tamaño cero.
Consulte empty base optimization para más detalles.
Cualquier objeto que no sea un campo de bits con tamaño distinto de cero debe ocupar uno o más bytes de almacenamiento, incluyendo cada byte que esté ocupado (total o parcialmente) por cualquiera de sus subobjetos. El almacenamiento ocupado debe ser contiguo si el objeto es de tipo trivialmente copiable o de diseño estándar (desde C++11) .
Dirección
A menos que un objeto sea un campo de bits o un subobjeto de tamaño cero, la dirección de ese objeto es la dirección del primer byte que ocupa.
Un objeto puede contener otros objetos, en cuyo caso los objetos contenidos están anidados dentro del objeto anterior. Un objeto a está anidado dentro de otro objeto b si se cumple alguna de las siguientes condiciones:
- a es un subobjeto de b .
- b proporciona almacenamiento para a .
- Existe un objeto c donde a está anidado dentro de c , y c está anidado dentro de b .
Un objeto es un objeto potencialmente no único si es uno de los siguientes objetos:
- Un objeto string literal .
|
(desde C++11) |
- Un subobjeto de un objeto potencialmente no único.
Para cualesquiera dos objetos que no sean campos de bits con tiempos de vida superpuestos:
- Si se cumple alguna de las siguientes condiciones, pueden tener la misma dirección:
-
- Uno de ellos está anidado dentro del otro.
- Cualquiera de ellos es un subobjeto de tamaño cero, y sus tipos no son similares .
- Ambos son objetos potencialmente no únicos.
- De lo contrario, siempre tienen direcciones distintas y ocupan bytes de almacenamiento disjuntos.
// los literales de carácter siempre son únicos static const char test1 = 'x'; static const char test2 = 'x'; const bool b = &test1 != &test2; // siempre verdadero // el carácter 'x' accedido desde “r”, “s” e “il” // puede tener la misma dirección (es decir, estos objetos pueden compartir almacenamiento) static const char (&r) [] = "x"; static const char *s = "x"; static std::initializer_list<char> il = {'x'}; const bool b2 = r != il.begin(); // resultado no especificado const bool b3 = r != s; // resultado no especificado const bool b4 = il.begin() != &test1; // siempre verdadero const bool b5 = r != &test1; // siempre verdadero
Objetos polimórficos
Los objetos de un tipo de clase que declara o hereda al menos una función virtual son objetos polimórficos. Dentro de cada objeto polimórfico, la implementación almacena información adicional (en todas las implementaciones existentes, es un puntero a menos que sea optimizado), que es utilizada por las llamadas a
funciones virtuales
y por las características de RTTI (
dynamic_cast
y
typeid
) para determinar, en tiempo de ejecución, el tipo con el que el objeto fue creado, independientemente de la expresión en la que se utilice.
Para objetos no polimórficos, la interpretación del valor se determina a partir de la expresión en la que se utiliza el objeto, y se decide en tiempo de compilación.
#include <iostream> #include <typeinfo> struct Base1 { // tipo polimórfico: declara un miembro virtual virtual ~Base1() {} }; struct Derived1 : Base1 { // tipo polimórfico: hereda un miembro virtual }; struct Base2 { // tipo no polimórfico }; struct Derived2 : Base2 { // tipo no polimórfico }; int main() { Derived1 obj1; // objeto1 creado con tipo Derived1 Derived2 obj2; // objeto2 creado con tipo Derived2 Base1& b1 = obj1; // b1 hace referencia al objeto obj1 Base2& b2 = obj2; // b2 hace referencia al objeto obj2 std::cout << "Expression type of b1: " << typeid(decltype(b1)).name() << '\n' << "Expression type of b2: " << typeid(decltype(b2)).name() << '\n' << "Object type of b1: " << typeid(b1).name() << '\n' << "Object type of b2: " << typeid(b2).name() << '\n' << "Size of b1: " << sizeof b1 << '\n' << "Size of b2: " << sizeof b2 << '\n'; }
Salida posible:
Expression type of b1: Base1 Expression type of b2: Base2 Object type of b1: Derived1 Object type of b2: Base2 Size of b1: 8 Size of b2: 1
Alias estricto
Acceder a un objeto usando una expresión de un tipo diferente al tipo con el que fue creado es comportamiento indefinido en muchos casos, consulte
reinterpret_cast
para la lista de excepciones y ejemplos.
Alineación
Cada tipo de objeto tiene la propiedad llamada requisito de alineación , que es un valor entero no negativo (de tipo std::size_t , y siempre una potencia de dos) que representa el número de bytes entre direcciones sucesivas en las que se pueden asignar objetos de este tipo.
|
El requisito de alineación de un tipo puede consultarse con
|
(desde C++11) |
Cada tipo de objeto impone su requisito de alineación sobre cada objeto de ese tipo
; se puede solicitar una alineación más estricta (con un requisito de alineación mayor) usando
alignas
(desde C++11)
. Intentar crear un objeto en almacenamiento que no cumple con los requisitos de alineación del tipo del objeto es comportamiento indefinido.
Para satisfacer los requisitos de alineación de todos los miembros no estáticos de una class , padding bits pueden insertarse después de algunos de sus miembros.
#include <iostream> // los objetos de tipo S pueden asignarse en cualquier dirección // porque tanto S.a como S.b pueden asignarse en cualquier dirección struct S { char a; // tamaño: 1, alineación: 1 char b; // tamaño: 1, alineación: 1 }; // tamaño: 2, alineación: 1 // los objetos de tipo X deben asignarse en límites de 4 bytes // porque X.n debe asignarse en límites de 4 bytes // porque el requisito de alineación de int es (normalmente) 4 struct X { int n; // tamaño: 4, alineación: 4 char c; // tamaño: 1, alineación: 1 // tres bytes de relleno }; // tamaño: 8, alineación: 4 int main() { std::cout << "alignof(S) = " << alignof(S) << '\n' << "sizeof(S) = " << sizeof(S) << '\n' << "alignof(X) = " << alignof(X) << '\n' << "sizeof(X) = " << sizeof(X) << '\n'; }
Salida posible:
alignof(S) = 1 sizeof(S) = 2 alignof(X) = 4 sizeof(X) = 8
La alineación más débil (el requisito de alineación más pequeño) es la alineación de char , signed char , y unsigned char , que es igual a 1 ; la mayor alineación fundamental de cualquier tipo está definida por la implementación y es igual a la alineación de std::max_align_t (desde C++11) .
Las alineaciones fundamentales son compatibles para objetos de todo tipo de duración de almacenamiento.
|
Si la alineación de un tipo se hace más estricta (más grande) que
std::max_align_t
usando
Allocator se requiere que los tipos manejen correctamente los tipos sobrealineados. |
(since C++11) |
|
Está definido por la implementación si new expressions y (until C++17) std::get_temporary_buffer admiten tipos sobrealineados. |
(since C++11)
(until C++20) |
Notas
Los objetos en C++ tienen un significado diferente al de los objetos en programación orientada a objetos (POO) :
| Objetos en C++ | Objetos en POO |
|---|---|
|
pueden tener cualquier tipo de objeto
(ver std::is_object ) |
deben tener un tipo de clase |
| no existe el concepto de "instancia" |
tienen el concepto de "instancia" (y existen mecanismos como
instanceof
para detectar la relación "instancia-de")
|
| no existe el concepto de "interfaz" |
tienen el concepto de "interfaz" (y existen mecanismos como
instanceof
para detectar si una interfaz está implementada)
|
| el polimorfismo debe habilitarse explícitamente mediante miembros virtuales | el polimorfismo está siempre habilitado |
En el informe de defectos
P0593R6
, se consideró que la creación implícita de objetos ocurría al crear un array de bytes o invocar una
función de asignación
(que posiblemente está definida por el usuario y es
constexpr
). Sin embargo, dicha permisividad causaba indeterminismo en la evaluación constante, lo cual era indeseado e inimplementable en algunos aspectos. Como resultado,
P2747R2
prohibió dicha creación implícita de objetos en la evaluación constante. Intencionalmente tratamos este cambio como un informe de defectos aunque el documento completo no lo sea.
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares de C++ publicados anteriormente.
| DR | Se aplica a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 633 | C++98 | las variables solo podían ser objetos | también pueden ser referencias |
| CWG 734 | C++98 |
no estaba especificado si las variables definidas
en el mismo ámbito que están garantizadas de tener el mismo valor pueden tener la misma dirección |
la dirección está garantizada de ser
diferente si sus tiempos de vida se superponen, independientemente de sus valores |
| CWG 1189 | C++98 |
dos subobjetos de clase base del mismo
tipo podían tener la misma dirección |
siempre tienen
direcciones distintas |
| CWG 1861 | C++98 |
para campos de bits de tamaño excesivo de tipos de carácter
estrecho, todos los bits de la representación del objeto aún participaban en la representación del valor |
permite bits de relleno |
| CWG 2489 | C++98 |
char
[
]
no puede proporcionar almacenamiento, pero los objetos
podían crearse implícitamente dentro de su almacenamiento |
los objetos no pueden crearse implícitamente
dentro del almacenamiento de char [ ] |
| CWG 2519 | C++98 | la definición de representación del objeto no abordaba los campos de bits | aborda los campos de bits |
| CWG 2719 | C++98 |
el comportamiento de crear un objeto
en almacenamiento desalineado no estaba claro |
el comportamiento es
indefinido en este caso |
| CWG 2753 | C++11 |
no estaba claro si un array de respaldo de una
lista de inicializadores puede compartir almacenamiento con un literal de cadena |
pueden compartir almacenamiento |
| CWG 2795 | C++98 |
al determinar si dos objetos con tiempos de vida superpuestos
pueden tener la misma dirección, si alguno de ellos es un subobjeto de tamaño cero, podían tener tipos distintos similares |
solo permite tipos no similares |
| P0593R6 | C++98 |
el modelo de objeto anterior no admitía muchos
modismos útiles requeridos por la biblioteca estándar y no era compatible con los tipos efectivos en C |
se añadió creación implícita de objetos |
Véase también
|
Documentación de C
para
Object
|