Namespaces
Variants

Object

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Los programas de C++ crean, destruyen, refieren, acceden y manipulan objetos .

Un objeto, en C++, tiene

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:
(desde C++17)
(desde C++20)
  • llamada a las siguientes funciones específicas, en cuyo caso dichos objetos se crean en la región de almacenamiento especificada:
  • std::start_lifetime_as
  • std::start_lifetime_as_array
(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,

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:

(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 alignof o std::alignment_of . La función de alineación de punteros std::align puede utilizarse para obtener un puntero adecuadamente alineado dentro de un búfer. std::aligned_storage puede utilizarse para obtener almacenamiento adecuadamente alineado. (hasta C++23)

(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 alignas , se conoce como un tipo con requisito de alineación extendida . Un tipo cuya alineación es extendida o un tipo de clase cuyo miembro de datos no estático tiene alineación extendida es un tipo sobrealineado .

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