Пространства имён
Варианты
Действия

Object

Материал из cppreference.com
< cpp‎ | language

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

В C++ объект - это область памяти, которая характеризуется:

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

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

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

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

Содержание

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

Для объекта типа T, представление объекта это последовательность из sizeof(T) объектов типа unsigned char (или эквивалентного типа std::byte), начинающаяся с того же адреса, что и сам объект типа T.

Представление значения объекта это множество битов, которое содержит значение его типа T.

Для TriviallyCopyable-типов, представление значения это часть представления объекта, что означает, что копирования байтов памяти, занимаемых объектом, будет достаточно для создания другого объекта с тем же значением. Исключение составляет случай, когда это значение является особым представлением (trap representation) для своего типа, такое как значение SNaN ("signalling not-a-number") для чисел с плавающей точкой или значение NaT ("not-a-thing") для целых чисел, и загрузка такого значения в ЦПУ приведёт к генерации аппаратного исключения.

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

#include <cassert>
struct S {
    char c;  // 1 байт значения
             // 3 байта заполнения
    float f; // 4 байта значения (в предположении, что sizeof(float) == 4)
    bool operator==(const S& arg) const { // равенство по значению
        return c == arg.c && f == arg.f;
    }
};
assert(sizeof(S) == 8);
S s1 = {'a', 3.14};
S s2 = s1;
reinterpret_cast<char*>(&s1)[2] = 'b'; // изменить 2-й байт
assert(s1 == s2); // значение не измененилось

Для объектов типа char, signed char, и unsigned char (если только они не битовые поля большего размера), каждый бит представления объекта должен участвовать в представлении значения и каждый возможный битовый шаблон предствляет отдельное значение (заполнения, trap-биты или множественные представления не разрешены).

[править] Подобъекты

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

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

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

Подобъект является потенциально перекрывающимся, если он

  • подобъект базового класса, или
  • нестатический член-данное, объявленный с атрибутом [[no_unique_address]].

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

Любые два объекта с пересекащимися временами жизни (не являющиеся битовыми полями) гарантированно имеют разные адреса, если только один из них не является подобъектом другого или предоставляет память для размещения другого, или объекты являются подобъектами разных типов в составе одного полного объекта, и один из них - подобъект базового класса нулевого размера.

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() << ' '
              << "Тип b2, определяемый выражением: " << typeid(decltype(b2)).name() << '\n'
              << "Объектный тип b1: " << typeid(b1).name() << ' '
              << "Объектный тип b2: " << typeid(b2).name() << '\n'
              << "размер b1: " << sizeof b1 << ' '
              << "размер 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

Самое слабое выравнивание - 1 байт (наименьшее требование по выравниванию) - имеют типы char, signed char, и unsigned char; самое широкое базовое выравнивание любого встроенного типа определяется выравниванием типа std::max_align_t. Если через alignas выравнивание некоторого типа задано строже (шире) выравнивания std::max_align_t, такой тип называют типом с расширенным выравниванием. Тип, выравнивание которого было расширено, или классовый тип, нестатический член-данное которого имеет расширенное выравнивание, является типом с расширенным выравниванием. Поддерживают ли new-выражение, std::allocator::allocate, и std::get_temporary_buffer типы с расширенным выравниванием, определяется реализацией. Инстанцированиям Allocatorов, параметризованных типом с расширенным выравниванием, разрешается заканчиваться неудачей во время компиляции, возбуждать исключение std::bad_alloc во время выполнения, игнорировать неподдерживаемое требование по выравниванию без выдачи сообщения об ошибке или кореектно их обрабатывать.

[править] Смотри также