Spec-Zone.ru › C++

Объект

Программы на C++ создают, уничтожают, используют ссылки на объекты, обращаются к ним и манипулируют ими.

Объект в C++ имеет

  • размер (можно определить с помощью sizeof);
  • требование к выравниванию (можно определить с помощью alignof);
  • длительность хранения (автоматическую, статическую, динамическую, локальную для потока);
  • время жизни (ограничено длительностью хранения или является временным);
  • тип;
  • значение (которое может быть неопределённым, например у типов, не являющихся классами, с инициализацией по умолчанию);
  • необязательно, имя.

Следующие сущности не являются объектами: значение, ссылка, функция, перечислитель, тип, нестатический член класса, шаблон, специализация шаблона класса или функции, пространство имён, пакет параметров и this.

Переменная — это объект или ссылка, не являющиеся нестатическим членом данных, которые вводятся посредством объявления.

Создание объекта

Объекты можно явно создавать с помощью определений, new-выражений, throw-выражений, изменяя активный член объединения и вычисляя выражения, требующие временных объектов. Создаваемый объект однозначно определяется при явном создании объекта.

Объекты типов с неявным временем жизни также могут неявно создаваться посредством

  • за исключением вычисления константного выражения, операций, начинающих время жизни массива типа unsignedchar или std::byte(начиная с C++17); в этом случае такие объекты создаются в массиве;
  • вызова следующих функций выделения памяти; в этом случае такие объекты создаются в выделенной области памяти:
    • operator new (за исключением вычисления константного выражения)
    • operator new[] (за исключением вычисления константного выражения)
    • std::malloc
    • std::calloc
    • std::realloc
  • std::aligned_alloc
(начиная с C++17)
  • вызова следующих функций копирования представления объекта; в этом случае такие объекты создаются в целевой области памяти или в результате:
    • std::memcpy
    • std::memmove
  • std::bit_cast
(начиная с C++20)
  • вызова следующих специальных функций; в этом случае такие объекты создаются в указанной области памяти:
    • std::start_lifetime_as
    • std::start_lifetime_as_array
(начиная с C++23)

В одной и той же области памяти может быть создано ноль или более объектов, если это обеспечивает определённое поведение программы. Если такое создание невозможно, например из-за конфликтующих операций, поведение программы не определено. Если несколько таких наборов неявно создаваемых объектов обеспечивают определённое поведение программы, не указано, какой именно набор объектов будет создан. Иными словами, не требуется, чтобы неявно создаваемые объекты определялись однозначно.

После неявного создания объектов в заданной области памяти некоторые операции возвращают указатель на подходящий созданный объект. Подходящий созданный объект имеет тот же адрес, что и область памяти. Аналогично, поведение не определено тогда и только тогда, когда ни одно такое значение указателя не может обеспечить определённое поведение программы; если же таких значений несколько, не указано, какое именно значение указателя будет получено.

#include <cstdlib>

struct X { int a, b; };

X* MakeX()
{
    // One of possible defined behaviors:
    // the call to std::malloc implicitly creates an object of type X
    // and its subobjects a and b, and returns a pointer to that X object
    X* p = static_cast<X*>(std::malloc(sizeof(X)));
    p->a = 1;
    p->b = 2;
    return p;
}

Вызов std::allocator::allocate или неявно определённых специальных функций-членов копирования/перемещения типов объединений также может создавать объекты.

Представление объекта и представление значения

Некоторые типы и объекты имеют представления объекта и представления значения, определённые в таблице ниже:

Сущность Представление объекта Представление значения
тип полного объекта T последовательность из N объектов типа unsignedchar, занимаемых полным объектом типа T, не являющимся битовым полем, где N — это sizeof(T) множество битов в представлении объекта T, участвующих в представлении значения типа T
полный объект obj типа T, не являющийся битовым полем байты объекта obj, соответствующие представлению объекта T биты объекта obj, соответствующие представлению значения T
объект-битовое поле bf последовательность из N битов, занимаемых bf, где N — ширина битового поля множество битов в представлении объекта bf, участвующих в представлении значения bf

Биты в представлении объекта типа или объекта, не входящие в представление значения, являются битами заполнения.

Для типов TriviallyCopyable представление значения является частью представления объекта, а это означает, что копирования байтов, занимаемых объектом в памяти, достаточно для создания другого объекта с тем же значением (за исключением случая, когда объект является потенциально перекрывающимся подобъектом или значение представляет собой ловушечное представление своего типа и его загрузка в ЦП вызывает аппаратное исключение, например для значений с плавающей точкой SNaN («сигнальное не число») или целых чисел NaT («не вещь»)).

Хотя большинство реализаций не допускают ловушечных представлений, битов заполнения или нескольких представлений целочисленных типов, существуют исключения; например, значение целочисленного типа на Itanium может быть ловушечным представлением.

Обратное утверждение не обязательно верно: два объекта типа TriviallyCopyable с разными представлениями объектов могут представлять одно и то же значение. Например, несколько битовых шаблонов чисел с плавающей точкой представляют одно и то же специальное значение NaN. Чаще всего биты заполнения могут добавляться для соблюдения требований к выравниванию, размеров битовых полей и т. д.

#include <cassert>

struct S
{
    char c;  // 1 byte value
             // 3 bytes of padding bits (assuming alignof(float) == 4)
    float f; // 4 bytes value (assuming sizeof(float) == 4)
    
    bool operator==(const S& arg) const // value-based equality
    {
        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'; // modify some padding bits
    assert(s1 == s2); // value did not change
}

Для объектов типов char, signedchar, unsignedchar, char8_t(начиная с C++20) (если они не являются увеличенными битовыми полями) каждый бит представления объекта должен участвовать в представлении значения, и каждый возможный битовый шаблон представляет отдельное значение (биты заполнения, ловушечные биты и множественные представления не допускаются).

Подобъекты

У объекта могут быть подобъекты. К ним относятся

  • объекты-члены
  • подобъекты базовых классов
  • элементы массива

Объект, не являющийся подобъектом другого объекта, называется полным объектом.

Если полный объект, подобъект-член или элемент массива имеет тип класса, его тип считается наиболее производным классом , чтобы отличать его от типа класса любого подобъекта базового класса. Объект типа наиболее производного класса или типа, не являющегося классом, называется наиболее производным объектом .

Для класса

  • его нестатические члены данных,
  • его невиртуальные непосредственные базовые классы и
  • если класс не является абстрактным, его виртуальные базовые классы

называются его потенциально конструируемыми подобъектами.

Размер

Подобъект является потенциально перекрывающимся подобъектом, если он является подобъектом базового класса или нестатическим членом данных, объявленным с атрибутом [[no_unique_address]](начиная с C++20).

Объект obj может иметь нулевой размер только при выполнении всех следующих условий:

  • obj является потенциально перекрывающимся подобъектом.
  • obj имеет тип класса без виртуальных функций-членов и виртуальных базовых классов.
  • obj не имеет подобъектов ненулевого размера или безымянных битовых полей ненулевой длины.

Для объекта obj, удовлетворяющего всем перечисленным выше условиям:

  • Если obj является подобъектом базового класса типа класса стандартного расположения(начиная с C++11), не имеющего нестатических членов данных, его размер равен нулю.
  • В противном случае обстоятельства, при которых obj имеет нулевой размер, определяются реализацией.

Подробнее см. оптимизацию пустого базового класса.

Любой объект, не являющийся битовым полем и имеющий ненулевой размер, должен занимать один или несколько байтов памяти, включая каждый байт, полностью или частично занимаемый любым из его подобъектов. Занимаемая память должна быть непрерывной, если объект имеет тип trivially copyable или стандартного расположения(начиная с C++11).

Адрес

Если объект не является битовым полем или подобъектом нулевого размера, его адрес — это адрес первого занимаемого им байта.

Объект может содержать другие объекты; в таком случае содержащиеся объекты являются вложенными в этот объект. Объект a вложен в другой объект b, если выполняется одно из следующих условий:

  • a является подобъектом b.
  • b предоставляет память для a.
  • Существует объект c, где a вложен в c, а c вложен в b.

Объект является потенциально неуникальным объектом, если он относится к одному из следующих объектов:

  • Объект строкового литерала.
  • Поддерживающий массив списка инициализаторов.
(начиная с C++11)
  • Объект параметра шаблона типа массива (см. std::meta::reflect_constant_string и std::meta::reflect_constant_array).
(начиная с C++26)
  • Подобъект потенциально неуникального объекта.

Для любых двух объектов, не являющихся битовыми полями, с перекрывающимися временами жизни:

  • Если выполняется одно из следующих условий, они могут иметь один и тот же адрес:
    • Один из них вложен в другой.
    • Любой из них является подобъектом нулевого размера, а их типы не являются сходными.
    • Оба являются потенциально неуникальными объектами.
  • В противном случае их адреса всегда различны, и они занимают непересекающиеся байты памяти.
// character literals are always unique
static const char test1 = 'x';
static const char test2 = 'x';
const bool b = &test1 != &test2;      // always true

// the character 'x' accessed from “r”, “s” and “il”
// may have the same address (i.e., these objects may share storage)
static const char (&r) [] = "x";
static const char *s = "x";
static std::initializer_list<char> il = {'x'};
const bool b2 = r != il.begin();      // unspecified result
const bool b3 = r != s;               // unspecified result
const bool b4 = il.begin() != &test1; // always true
const bool b5 = r != &test1;          // always true

Полиморфные объекты

Объекты типа класса, который объявляет или наследует хотя бы одну виртуальную функцию, являются полиморфными объектами. В каждом полиморфном объекте реализация хранит дополнительную информацию (во всех существующих реализациях это один указатель, если только он не был оптимизирован), которая используется при вызовах виртуальных функций и при использовании возможностей RTTI (dynamic_cast и typeid), чтобы во время выполнения определить тип, с которым был создан объект, независимо от выражения, в котором он используется.

Для неполиморфных объектов интерпретация значения определяется выражением, в котором используется объект, и устанавливается во время компиляции.

#include <iostream>
#include <typeinfo>

struct Base1
{
    // polymorphic type: declares a virtual member
    virtual ~Base1() {}
};

struct Derived1 : Base1
{
     // polymorphic type: inherits a virtual member
};

struct Base2
{
     // non-polymorphic type
};

struct Derived2 : Base2
{
     // non-polymorphic type
};

int main()
{
    Derived1 obj1; // object1 created with type Derived1
    Derived2 obj2; // object2 created with type Derived2
    
    Base1& b1 = obj1; // b1 refers to the object obj1
    Base2& b2 = obj2; // b2 refers to the object 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';
}

Возможный вывод:

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

Строгая типизация псевдонимов

Во многих случаях доступ к объекту через выражение типа, отличного от типа, с которым он был создан, приводит к неопределённому поведению; список исключений и примеры см. в разделе reinterpret_cast.

Выравнивание

Каждый тип объекта обладает свойством, называемым требованием к выравниванию, которое представляет собой неотрицательное целое значение (типа std::size_t, всегда являющееся степенью двойки), обозначающее число байтов между последовательными адресами, по которым могут размещаться объекты этого типа.

Требование к выравниванию типа можно запросить с помощью alignof или std::alignment_of. Функцию выравнивания указателя std::align можно использовать, чтобы получить указатель с подходящим выравниванием внутри буфера. std::aligned_storage можно использовать, чтобы получить память с подходящим выравниванием.(до C++23)

(начиная с C++11)

Каждый тип объекта предъявляет своё требование к выравниванию для всех объектов этого типа; можно запросить более строгое выравнивание (с большим требованием к выравниванию) с помощью alignas(начиная с C++11). Попытка создать объект в памяти, не удовлетворяющей требованиям к выравниванию типа объекта, приводит к неопределённому поведению.

Для удовлетворения требований к выравниванию всех нестатических членов класса после некоторых его членов могут вставляться биты заполнения.

#include <iostream>

// objects of type S can be allocated at any address
// because both S.a and S.b can be allocated at any address
struct S
{
    char a; // size: 1, alignment: 1
    char b; // size: 1, alignment: 1
}; // size: 2, alignment: 1

// objects of type X must be allocated at 4-byte boundaries
// because X.n must be allocated at 4-byte boundaries
// because int's alignment requirement is (usually) 4
struct X
{
    int n;  // size: 4, alignment: 4
    char c; // size: 1, alignment: 1
    // three bytes of padding bits
}; // size: 8, alignment: 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';
}

Возможный вывод:

alignof(S) = 1
sizeof(S)  = 2
alignof(X) = 4
sizeof(X)  = 8

Наименее строгое выравнивание (наименьшее требование к выравниванию) — это выравнивание char, signedchar и unsignedchar, равное 1; наибольшее фундаментальное выравнивание скалярного типа определяется реализацией и равно выравниванию std::max_align_t(начиная с C++11).

Если реализация не поддерживает запрос конкретного расширенного выравнивания в конкретном контексте, программа некорректна.

Если выравнивание типа строже (больше), чем std::max_align_t, оно называется типом с требованием расширенного выравнивания. Тип с расширенным выравниванием или тип класса, нестатический член данных которого имеет расширенное выравнивание, называется типом с повышенным выравниванием.

Типы Allocator должны корректно обрабатывать типы с повышенным выравниванием.

(начиная с C++11)

Поддерживают ли new-выражения и(до C++17) std::get_temporary_buffer типы с повышенным выравниванием, определяется реализацией.

(начиная с C++11)
(до C++20)

Примечания

Объекты в C++ имеют иное значение, чем объекты в объектно-ориентированном программировании (ООП):

Объекты в C++ Объекты в ООП
могут иметь любой тип объекта
(см. std::is_object)
должны иметь тип класса
понятие «экземпляр» отсутствует есть понятие «экземпляр» (и существуют механизмы, такие как instanceof, для определения отношения «экземпляр класса»)
понятие «интерфейс» отсутствует есть понятие «интерфейс» (и существуют механизмы, такие как instanceof, для определения того, реализован ли интерфейс)
полиморфизм необходимо явно включать с помощью виртуальных членов полиморфизм включён всегда

В отчёте об ошибке P0593R6 считалось, что неявное создание объектов происходит при создании массива байтов или вызове функции выделения памяти (которая может быть определена пользователем и constexpr) во время вычисления константного выражения. Однако это допущение приводило к недетерминированности при вычислении константных выражений, что было нежелательно и в некоторых аспектах не поддавалось реализации. В результате P2747R2 запретил такое неявное создание объектов при вычислении константных выражений. Мы намеренно рассматриваем это изменение как отчёт об ошибке, хотя весь документ таковым не является.

Отчёты об ошибках

Следующие отчёты об ошибках, изменяющие поведение, были применены задним числом к ранее опубликованным стандартам C++.

DR Применяется к Поведение согласно опубликованному стандарту Корректное поведение
CWG 633 C++98 переменными могли быть только объекты ими также могут быть ссылки
CWG 734 C++98 не было указано, могут ли переменные, определённые
в одной области видимости и гарантированно имеющие
одинаковые значения, иметь один и тот же адрес
гарантируется, что адреса
различны, если их времена жизни
перекрываются, независимо от значений
CWG 1189 C++98 два подобъекта базовых классов одного
типа могли иметь один и тот же адрес
их адреса всегда
различны
CWG 1861 C++98 для увеличенных битовых полей узких символьных
типов все биты представления объекта по-прежнему
участвовали в представлении значения
разрешены биты заполнения
CWG 2489 C++98 char[] не может предоставлять память, но объекты
могли неявно создаваться в занимаемой им памяти
объекты не могут неявно создаваться
в памяти, занимаемой char[]
CWG 2519 C++98 определение представления объекта не учитывало битовые поля учитывает битовые поля
CWG 2719 C++98 поведение при создании объекта
в невыровненной памяти было неясным
в этом случае поведение
не определено
CWG 2753 C++11 было неясно, может ли поддерживающий массив списка
инициализаторов совместно использовать память со строковым литералом
они могут совместно использовать память
CWG 2795 C++98 при определении того, могут ли два объекта с перекрывающимися
временами жизни иметь один и тот же адрес, если любой из них является
подобъектом нулевого размера, они могли иметь сходные различные типы
разрешены только несходные типы
P0593R6 C++98 предыдущая модель объектов не поддерживала многие
полезные идиомы, необходимые стандартной библиотеке,
и была несовместима с эффективными типами в C
добавлено неявное создание объектов

См. также

Документация по C для объекта

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/object

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API