#новичкам
В прошлый раз у нас была довольно простая С-like структура, для которой мы смотрели стандартные свойства.
struct A {
int a;
int b;
int c;
};Сегодня посмотрим, какие свойства типа изменятся, если совсем чуть-чуть изменить структуру.
🔀 Добавим просто std::string в качестве поля структуры.
struct A {
int a;
int b;
int c;
std::string s;
};Из-за того, что у std::string нетривиальные специальное методы классов, то это автоматически делает нетривиальными специальные методы у
A, поэтому отличие будет только в следующих трейтах:static_assert(!std::is_trivially_copyable_v<A>);
static_assert(!std::is_trivially_destructible_v<A>);
static_assert(!std::is_trivially_copy_constructible_v<A>);
static_assert(!std::is_trivially_move_constructible_v<A>);
static_assert(!std::is_trivially_copy_assignable_v<A>);
static_assert(!std::is_trivially_move_assignable_v<A>);
static_assert(!std::is_trivially_default_constructible_v<A>);
🔀 Добавим виртуальный метод
struct A {
int a;
int b;
int c;
virtual void foo() {}
};Очевидно, тип сразу же стал полиморфным. Но и много чего потерял. Чисто по определению некоторых свойств полиморфный тип не может им удовлетворять. Это:
👉🏿 std::is_standard_layout - грубо говоря, тип становится невместим с С
👉🏿 std::is_trivially_copyable - тривиальное копирование подразумевает простое копирование по байтам. Копирование полиморфного типа нельзя производить побайтово. Компилятор обязан либо скопировать vptr как есть (если это тот же тип), либо установить правильный vptr нового типа (если копируем через базовый класс — slicing). Это уже нетривиальная логика.
👉🏿 std::is_trivially_default_constructible - конструктор по умолчанию не тривиальный, так как надо инициализировать vptr в правильную vtable.
👉🏿 std::is_aggregate - как только у класса появляется приватный метод(vptr в данном случае), он уже не может быть агрегатом.
static_assert(std::is_polymorphic_v<Poly>);
static_assert(!std::is_standard_layout_v<Poly>);
static_assert(!std::is_trivially_copyable_v<Poly>);
static_assert(!std::is_trivially_default_constructible_v<Poly>);
static_assert(!std::is_aggregate_v<Poly>);
🔀 Более менее понятно, что будет, если добавить пользовательский конструктор. А что если добавить кастомный деструктор?
struct A {
int a;
int b;
int c;
~A() {}
};
static_assert(!std::is_trivially_destructible_v<A>);
static_assert(!std::is_trivially_copyable_v<A>);
static_assert(!std::is_trivially_copy_constructible_v<A>);
static_assert(!std::is_trivially_move_constructible_v<A>);
static_assert(std::is_aggregate_v<A>);
static_assert(std::is_standard_layout_v<A>);Он никак не влияет на layout объекта и на способность объекта быть созданным агрегацией. Однако объект перестает быть тривиально копируемым и создаваемым копией или перемещением. С перемещением понятно - оно не генерится с кастомным деструктором.
А с копированием интересно. Стандарт говорит, что при установке требований на создание объекта нужно учитывать и разрушение объекта. Поэтому объект типа А нельзя создать тривиально копией(хотя конструктор копирования продолжает генериться компилятором и быть тривиальным).
Для is_trivially_copyable нужен дефолтный деструктор, чтобы не было никакой дополнительной логики освобождения ресурсов при побайтовом копировании одного объекта в другой.
Это все звучит, как ненужные и неважные детали. Но мы здесь грокаем С++. И просто интересно, почему, с точки зрения архитектуры языка, ломается тривиальность дефолтного конструктора при добавлении виртуальной функции.
Know your traits. Stay cool.
#template #cppcore