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 последовательность объектов типа unsignedchar размером N, занимаемая полным объектом типа 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 имеет нулевой размер, определяются реализацией.

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

Любой объект, не являющийся битовым полем и имеющий ненулевой размер, должен занимать один или более байтов памяти, включая каждый байт, занимаемый (полностью или частично) любым из его подобъектов. Занимаемая память должна быть непрерывной, если объект имеет тривиально копируемый или стандартный для размещения(начиная с 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/objects

Spec-Zone.ru

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