Object
Программы на C++ создают, уничтожают, ссылаются на, получают доступ и манипулируют объектами .
Объект в C++ имеет
-
размер (можно определить с помощью
sizeof); -
требование выравнивания (можно определить с помощью
alignof); - продолжительность хранения (автоматическая, статическая, динамическая, локальная для потока);
- время жизни (ограничено продолжительностью хранения или временное);
- тип ;
- значение (которое может быть неопределённым, например, для инициализированных по умолчанию неклассовых типов);
- опционально, имя .
Следующие сущности не являются объектами: значение, ссылка, функция, перечислитель, тип, нестатический член класса, шаблон, специализация шаблона класса или функции, пространство имён, пакет параметров и this .
Переменная — это объект или ссылка, не являющаяся нестатическим членом-данных, которая вводится объявлением .
Содержание |
Создание объектов
Объекты могут быть явно созданы с помощью определений , new выражений , throw выражений , изменения активного члена union и вычисления выражений, требующих временных объектов . Созданный объект однозначно определяется при явном создании объекта.
Объекты типов с неявным временем жизни также могут быть неявно созданы с помощью
- за исключением константного вычисления, операции, которые начинают время жизни массива типа unsigned char или std::byte (начиная с C++17) , в этом случае такие объекты создаются в массиве,
- вызов следующих функций выделения памяти, в этом случае такие объекты создаются в выделенной памяти:
-
- operator new (кроме случаев вычисления на этапе компиляции)
- operator new[] (кроме случаев вычисления на этапе компиляции)
- std::malloc
- std::calloc
- std::realloc
| (начиная с C++17) |
- вызов следующих функций копирования объектного представления , в этом случае такие объекты создаются в целевой области памяти или в результате:
| (начиная с C++20) |
|
(since C++23) |
Ноль или более объектов могут быть созданы в одной и той же области памяти, при условии что это обеспечивает программе определенное поведение. Если такое создание невозможно, например, из-за конфликтующих операций, поведение программы не определено. Если несколько таких наборов неявно созданных объектов обеспечивают программе определенное поведение, не специфицируется, какой именно набор объектов создается. Другими словами, неявно созданные объекты не обязаны быть однозначно определенными.
После неявного создания объектов в указанной области памяти некоторые операции производят указатель на подходящий созданный объект . Подходящий созданный объект имеет тот же адрес, что и область памяти. Аналогично, поведение не определено, только если ни одно такое значение указателя не может обеспечить программе определенное поведение, и не указано, какое значение указателя производится, если существует несколько значений, обеспечивающих программе определенное поведение.
#include <cstdlib> struct X { int a, b; }; X* MakeX() { // Один из возможных определенных вариантов поведения: // вызов std::malloc неявно создает объект типа X // и его подобъекты a и b, и возвращает указатель на этот объект X X* p = static_cast<X*>(std::malloc(sizeof(X))); p->a = 1; p->b = 2; return p; }
Вызов std::allocator::allocate или неявно определенных специальных функций-членов копирования/перемещения union типов также может создавать объекты.
Представление объекта и представление значения
Некоторые типы и объекты имеют представления объектов и представления значений , они определены в таблице ниже:
| Сущность | Объектное представление | Знаковое представление |
|---|---|---|
полный объектный тип
T
|
последовательность из
N
unsigned
char
объектов, занимаемых полным объектом типа
T
, не являющимся
битовым полем
, где
N
равно
sizeof
(
T
)
|
набор битов в объектном представлении
T
, которые участвуют в представлении значения типа
T
|
полный объект, не являющийся битовым полем,
obj
типа
T
|
байты
obj
, соответствующие объектному представлению
T
|
биты
obj
, соответствующие знаковому представлению
T
|
| объект битового поля bf | последовательность из N битов, занимаемых bf , где N — ширина битового поля | набор битов в объектном представлении bf , которые участвуют в представлении значения bf |
Биты в представлении объекта типа или объекта, которые не являются частью представления значения, называются битами заполнения .
Для TriviallyCopyable типов представление значения является частью представления объекта, что означает, что копирования байтов, занимаемых объектом в памяти, достаточно для создания другого объекта с тем же значением (за исключением случаев, когда объект является потенциально перекрывающимся подобъектом, или значение является trap representation своего типа и его загрузка в процессор вызывает аппаратное исключение, такое как SNaN ("signalling not-a-number") для значений с плавающей точкой или NaT ("not-a-thing") для целых чисел).
Хотя большинство реализаций не допускают trap-представлений, битов заполнения или множественных представлений для целочисленных типов, существуют исключения; например, значение целочисленного типа на Itanium может быть trap-представлением .
Обратное не обязательно верно: два объекта TriviallyCopyable типа с различными представлениями объектов могут представлять одно и то же значение. Например, несколько битовых паттернов с плавающей запятой представляют одно и то же специальное значение NaN . Более распространённый случай — биты заполнения могут добавляться для удовлетворения требований выравнивания , размеров битовых полей и т.д.
#include <cassert> struct S { char c; // 1-байтовое значение // 3 байта заполнения (предполагая alignof(float) == 4) float f; // 4-байтовое значение (предполагая sizeof(float) == 4) bool operator==(const S& arg) const // сравнение по значению { 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'; // изменяем некоторые биты заполнения assert(s1 == s2); // значение не изменилось }
Для объектов типа char , signed char и unsigned char (если они не являются переразмерными битовыми полями ), каждый бит представления объекта должен участвовать в представлении значения, и каждая возможная битовая комбинация представляет уникальное значение (не допускаются биты заполнения, ловушки или множественные представления).
Подобъекты
Объект может иметь подобъекты . К ним относятся
- объекты-члены
- подобъекты базовых классов
- элементы массива
Объект, который не является подобъектом другого объекта, называется полным объектом .
Если полный объект, подобъект-член или элемент массива имеет классовый тип , его тип считается наиболее производным классом , чтобы отличать его от классового типа любого подобъекта базового класса. Объект наиболее производного классового типа или объект неклассового типа называется наиболее производным объектом .
Для класса,
- его нестатические члены данных ,
- его невиртуальные прямые базовые классы , и,
- если класс не является абстрактным , его виртуальные базовые классы
называются его потенциально конструируемыми подобъектами .
Размер
Подобъект является
потенциально перекрывающимся подобъектом
, если он является подобъектом базового класса
или нестатическим элементом данных, объявленным с атрибутом
[[
no_unique_address
]]
(начиная с C++20)
.
Объект obj может иметь нулевой размер только при выполнении всех следующих условий:
- obj является потенциально перекрывающимся подобъектом.
- obj имеет тип класса без виртуальных функций-членов и виртуальных базовых классов.
- obj не содержит подобъектов ненулевого размера или безымянных битовых полей ненулевой длины.
Для объекта obj удовлетворяющего всем вышеуказанным условиям:
- Если obj является подобъектом базового класса standard-layout (since C++11) типа без нестатических членов данных, он имеет нулевой размер.
- В противном случае, реализационно-определено, при каких обстоятельствах obj имеет нулевой размер.
См. empty base optimization для получения более подробной информации.
Любой объект, не являющийся битовым полем, с ненулевым размером должен занимать один или более байтов памяти, включая каждый байт, который занят (полностью или частично) любым из его подобъектов. Занимаемая память должна быть непрерывной, если объект является тривиально копируемым или стандартным по размещению (since C++11) типа.
Адрес
Если объект не является битовым полем или подобъектом нулевого размера, адрес этого объекта — это адрес первого байта , который он занимает.
Объект может содержать другие объекты, в этом случае содержащиеся объекты являются вложенными в предыдущий объект. Объект a вложен в другой объект b если выполняется любое из следующих условий:
- a является подобъектом b .
- b предоставляет хранилище для a .
- Существует объект c , где a вложен в c , и c вложен в b .
Объект является потенциально неуникальным объектом если он представляет собой один из следующих объектов:
- Объект string literal .
|
(since C++11) |
- Подобъект потенциально неуникального объекта.
Для любых двух объектов, не являющихся битовыми полями, с пересекающимися временами жизни :
- Если выполняется любое из следующих условий, они могут иметь одинаковый адрес:
-
- Один из них вложен в другой.
- Любой из них является подобъектом нулевого размера, и их типы не являются подобными .
- Оба являются потенциально неуникальными объектами.
- В противном случае они всегда имеют различные адреса и занимают непересекающиеся байты памяти.
// символьные литералы всегда уникальны static const char test1 = 'x'; static const char test2 = 'x'; const bool b = &test1 != &test2; // всегда true // символ 'x', доступный через "r", "s" и "il" // может иметь одинаковый адрес (т.е. эти объекты могут использовать общую память) static const char (&r) [] = "x"; static const char *s = "x"; static std::initializer_list<char> il = {'x'}; const bool b2 = r != il.begin(); // неопределенный результат const bool b3 = r != s; // неопределенный результат const bool b4 = il.begin() != &test1; // всегда true const bool b5 = r != &test1; // всегда true
Полиморфные объекты
Объекты классового типа, которые объявляют или наследуют хотя бы одну виртуальную функцию, являются полиморфными объектами. Внутри каждого полиморфного объекта реализация хранит дополнительную информацию (в каждой существующей реализации это один указатель, если не оптимизирован), которая используется
виртуальными функциями
и возможностями RTTI (
dynamic_cast
и
typeid
) для определения во время выполнения типа, с которым был создан объект, независимо от выражения, в котором он используется.
Для неполиморфных объектов интерпретация значения определяется из выражения, в котором объект используется, и решается на этапе компиляции.
#include <iostream> #include <typeinfo> struct Base1 { // полиморфный тип: объявляет виртуальный член virtual ~Base1() {} }; struct Derived1 : Base1 { // полиморфный тип: наследует виртуальный член }; struct Base2 { // не полиморфный тип }; struct Derived2 : Base2 { // не полиморфный тип }; int main() { Derived1 obj1; // объект1 создан с типом Derived1 Derived2 obj2; // объект2 создан с типом Derived2 Base1& b1 = obj1; // b1 ссылается на объект obj1 Base2& b2 = obj2; // b2 ссылается на объект 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
.
Выравнивание
Каждый object type обладает свойством, называемым alignment requirement , которое представляет собой неотрицательное целочисленное значение (типа std::size_t , и всегда являющееся степенью двойки), обозначающее количество байтов между последовательными адресами, по которым могут размещаться объекты данного типа.
|
Требование выравнивания типа может быть запрошено с помощью
|
(начиная с C++11) |
Каждый тип объекта накладывает свои требования к выравниванию на каждый объект этого типа
; более строгое выравнивание (с большим требованием к выравниванию) может быть запрошено с помощью
alignas
(начиная с C++11)
. Попытка создать объект в памяти, которая не соответствует требованиям к выравниванию типа объекта, является неопределенным поведением.
Для удовлетворения требований выравнивания всех нестатических членов класса , биты заполнения могут быть вставлены после некоторых его членов.
#include <iostream> // объекты типа S могут быть размещены по любому адресу // потому что и S.a, и S.b могут быть размещены по любому адресу struct S { char a; // размер: 1, выравнивание: 1 char b; // размер: 1, выравнивание: 1 }; // размер: 2, выравнивание: 1 // объекты типа X должны быть размещены по 4-байтным границам // потому что X.n должен быть размещен по 4-байтным границам // поскольку требование выравнивания для int (обычно) равно 4 struct X { int n; // размер: 4, выравнивание: 4 char c; // размер: 1, выравнивание: 1 // три байта заполнения }; // размер: 8, выравнивание: 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 , signed char и unsigned char , которое равно 1 ; наибольшее фундаментальное выравнивание любого типа определяется реализацией и равно выравниванию std::max_align_t (начиная с C++11) .
Фундаментальные выравнивания поддерживаются для объектов всех видов длительностей хранения.
|
Если выравнивание типа делается более строгим (большим), чем
std::max_align_t
с использованием
Allocator типы обязаны корректно обрабатывать сверхвыровненные типы. |
(since C++11) |
|
Определяется реализацией, поддерживают ли new expressions и (until C++17) std::get_temporary_buffer сверхвыровненные типы. |
(since C++11)
(until 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
для
Object
|