Объект
Программы на С++ создают, уничтожают, ссылаются на, получают доступ к, и манипулируют объектами.
Объект в С++ это область памяти, которая (до C++14) имеет
- размер (может быть определён при помощи
sizeof); - требование к выравниванию (может быть определено при помощи
alignof); - класс памяти (автоматический, статический, динамический, потоковый);
- время жизни (определяется классом памяти или существует как временный объект);
- тип;
- значение (которое может быть неопределённым, например для инициализируемых по умолчанию неклассовых типов);
- необязательно, имя.
Следующие сущности не являются объектами: значение, ссылка, функция, перечислитель, тип, нестатический член класса, шаблон, спецализация шаблона класса или функции, пространство имён, пакет параметров и this.
Переменная, это объект или ссылка, являющиеся нестатическими членами-данных, которые введены объявлением.
Содержание |
[править] Создание объекта
Объекты могут быть явно созданы посредством определений, выражений new, выражений throw, изменением активного элемента объединения и вычисляемыми выражениями, которые требуют временных объектов.
Объекты типов с неявным временем жизни могут также быть неявно созданы:
- операциями, которые являются началом времени жизни массива типа char, unsigned char, или std::byte, (начиная с C++17) в этом случае такие объекты создаются в массиве,
- вызовом следующих функций распределения памяти, в этом случае такие объекты создаются в распределённой памяти:
|
(начиная с C++17) |
- вызовом следующих функций копирования представления объекта, в этом случае такие объекты создаются в назначенной области памяти или результате:
|
(начиная с C++20) |
Ноль и больше объектов могут создаваться в одной области памяти, до тех пор, пока это даёт программе заданное поведение. Если такое создание невозможно, например, из-за конфликтующих операций, поведение программы не определено. Если несколько таких наборов неявно созданных объектов дадут программе заданное поведение, не определено, который набор объектов будет создан. Другими словами, неявно созданные объекты не требуют уникального определения.
После неявного создания объектов в указанной области памяти, некоторые операции создают указатель на подходящий созданный объект. Подходящий созданный объект имеет тот же адрес, что и область памяти. Аналогично, поведение не определено, если только никакое такое значение указателя не может дать программе заданное поведение, и не определено, какое значение указателя создаётся, если существует несколько значений, задающих определённое программой поведение.
#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 или неявно определённые специальные функции-члены копирования/перемещения типов объединений, также могут создавать объекты.
[править] Представление объекта и представление значения
Для объекта типа T:
- представление объекта, это последовательность из sizeof(T) объектов типа unsigned char (или, что эквивалентно, std::byte) (начиная с C++17), начинающаяся с того же адреса, что и сам объект типа
T. - представление значения объекта, это множество битов, которое содержит значение его типа
T, и - биты заполнения это биты в представлении объекта, которые не являются частью представления значения.
Для типов TriviallyCopyable, представление значения, это часть представления объекта, что означает, что копирования байтов памяти, занимаемых объектом, будет достаточно для создания другого объекта с тем же значением (за исключением, если значение является особым представлением (trap representation) для своего типа, такое как значение SNaN ("signalling not-a-number") для чисел с плавающей точкой или значение NaT ("not-a-thing") для целых чисел, и загрузка такого значения в ЦПУ вызывает аппаратное исключение).
Обратное не всегда верно: два объекта типа TriviallyCopyable, с разным объектным представлением, могут представлять одно и то же значение. Например, несколько битовых шаблонов для чисел с плавающей точкой представляют одно особое значение NaN. Чаще всего могут быть введены биты заполнения, чтобв соответствовать требованию по выравниванию, размерам битового поля и т.д.
#include <cassert> struct S { char c; // 1 байт значения // 3 байта битов заполнения float f; // 4 байта значения (в предположении, что alignof(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<char*>(&s1)[2] = 'b'; // изменить некоторые биты заполнения assert(s1 == s2); // значение не изменилось }
Для объектов типа char, signed char, и unsigned char (если только они не битовые поля), каждый бит представления объекта должен участвовать в представлении значения и каждый возможный битовый шаблон предствляет отдельное значение (не допускаются биты заполнения, биты прерывания или множественные представления).
[править] Подобъекты
Объект может содержать другие объекты, которые называются подобъектами, а именно:
- объекты-элементы
- подобъекты базового класса
- элементы массива
Объект, не являющийся подобъектом другого объекта, называется полным объектом.
Подобъект является потенциально перекрывающимся, если он подобъект базового класса или нестатический элемент-данных, объявленный с атрибутом [[no_unique_address]]. (начиная с C++20)
Полные объекты, объекты-члены и элементы массива также известны как самые производные объекты, чтобы отличать их от подобъектов базового класса. Размер объекта, который не является потенциально перекрывающимся, а также битовым полем, не должен быть нулевым (размер подобъекта базового класса может быть нулевым даже без [[no_unique_address]] (начиная с C++20): смотрите оптимизация пустого базового класса).
Объект может содержать другой объект, в этом случае содержащийся объект вложен в первый объект. Объект a вложен в другой объект b, если
-
aподобъектb, или bпредоставляет память дляa, или
- существует объект
c, гдеaвложен вc, иcвложен вb.
Любые два объекта с перекрывающимися временами жизни (не являющиеся битовыми полями) гарантированно имеют разные адреса, если только один из них не является подобъектом другого, или объекты являются подобъектами разных типов в составе одного полного объекта, и один из них подобъект нулевого размера.
static const char c1 = 'x'; static const char c2 = 'x'; assert(&c1 != &c2); // одинаковые значения, но разные адреса
[править] Полиморфные объекты
Объекты классового типа, в котором объявлена или унаследована по крайней мере одна виртуальная функция, являются полиморфными объектами. Вместе с каждым полиморфным объектом реализация хранит дополнительную иформацию (во всех существующих реализациях, это просто указатель, если он не был удалён при оптимизации), который используется при вызове виртуальных функций и средств RTTI, таких как (dynamic_cast и typeid) для определения, во время работы, типа, с которым объект был создан, независимо от выражения, в котором он используется.
Для неполиморфных объектов, значение интерпретируется в зависимости от выражения, в котором используется объект, и определяется во время компиляции.
#include <iostream> #include <typeinfo> struct Base1 { // полиморфный тип: объявляет виртуальный элемент virtual ~Base1() {} }; struct Derived1 : Base1 { // полиморфный тип: наследует виртуальный элемент }; struct Base2 { // неполиморфный тип }; struct Derived2 : Base2 { // неполиморфный тип }; int main() { Derived1 obj1; // создание объекта типа Derived1 Derived2 obj2; // создание объекта типа Derived2 Base1& b1 = obj1; // b1 ссылается на объект obj1 Base2& b2 = obj2; // b2 ссылается на объект obj2 std::cout << "Тип выражения b1: " << typeid(decltype(b1)).name() << '\n' << "Тип выражения b2: " << typeid(decltype(b2)).name() << '\n' << "Тип объекта b1: " << typeid(b1).name() << '\n' << "Тип объекта b2: " << typeid(b2).name() << '\n' << "Размер b1: " << sizeof b1 << '\n' << "Размер b2: " << sizeof b2 << '\n'; }
Возможный вывод:
Тип выражения b1: Base1 Тип выражения b2: Base2 Тип объекта b1: Derived1 Тип объекта b2: Base2 Размер b1: 8 Размер b2: 1
[править] Ограничения на псевдонимы (strict aliasing)
Доступ к объекту с помощью выражения с типом, отличным от того, с которым он был создан, во многих случаях приводит к неопределённому поведению, смотрите reinterpret_cast для получения списка исключений и примеров.
[править] Выравнивание
Каждый объектный тип обладает свойством, называемым требованием по выравниванию, которое является целым числом (типа std::size_t, всегда степень числа 2), представляющее собой число байтов между последовательными адресами, по которым могут быть размещены объекты данного типа. Требование по выравниванию для типа может быть определено с помощью alignof или std::alignment_of. Для выравнивания указателя может быть использована функция std::align, чтобы получить подходящим образом выравненный указатель в пределах некоторого буфера. Для получения подходящим образом выравненной памяти может быть использована функция std::aligned_storage.
Каждый объектный тип накладывает связанное с ним требование по выравниванию на каждый объект данного типа; более строгое выравнивание (с более широким требованием к выравниванию) может быть получено, применяя alignas.
Чтобы соответствовать требованиям по выравниванию всех нестатических элементов класса, биты заполнения могут быть вставлены после некоторых его элементов.
#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 << "sizeof(S) = " << sizeof(S) << " alignof(S) = " << alignof(S) << '\n'; std::cout << "sizeof(X) = " << sizeof(X) << " alignof(X) = " << alignof(X) << '\n'; }
Возможный вывод:
sizeof(S) = 2 alignof(S) = 1 sizeof(X) = 8 alignof(X) = 4
Самое слабое выравнивание (наименьшее требование к выравниванию) это char, signed char и unsigned char, что эквивалентно 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++:
| Номер | Применён | Поведение в стандарте | Корректное поведение |
|---|---|---|---|
| CWG 633 | C++98 | переменные могут быть только объектами | они также могут быть ссылками |
| CWG 734 | C++98 | не было указано, могут ли переменные, определённые в одной и той же области видимости, и гарантированно имеющие одинаковое значение, иметь один и тот же адрес |
адрес гарантированно отличается, если их времена жизни перекрываются, независимо от их значений |
| CWG 1189 | C++98 | два подобъекта базового класса одного и того же типа могут иметь один и тот же адрес |
у них всегда разные адреса |
| CWG 1861 | C++98 | для больших битовых полей узких символьных типов все биты представления объекта по-прежнему участвуют в представлении значения |
допускаются биты заполнения |
| WG не указан | C++98 | предшествующая объектная модель не поддерживает много полезных идиом, требуемых стандартной библиотекой, и не совместима с эффективными типами в C |
добавлено неявное создание объектов |
[править] Смотрите также
| Документация по C для Объект
|

