В чем разница между тривиальным ctor (или dtor) и пользователем, заданным пустым ctor (или dtor)
Стандарт С++ определяет некоторые очень специфические поведения, когда класс имеет тривиальный конструктор и/или тривиальный деструктор.
В качестве примера, согласно § 3.8/1 стандарта:
Время жизни объекта типа T
заканчивается, когда:
- если T
- это тип класса с нетривиальным деструктором (12.4), начинается вызов деструктора или
- хранилище, которое занимает объект, повторно используется или освобождается.
Итак,
- Если объект не является тривиально разрушаемым, любая попытка доступа к элементам объекта после вызова деструктора - это UB.
- Если объект тривиально разрушаем, попытка доступа к элементам объекта после вызова деструктора является безопасным, а не UB.
Хотя этот пример может быть не лучшим, он показывает, что разница в поведении, возможно, имеет решающее значение (UB/non-UB), является ли объект тривиально разрушаемым или нет.
§12.4/3 Стандарта утверждает, что (для суммирования) деструктор класса T
тривиален, если он неявно определен, а не виртуальный, и если все базовые классы и члены класс T
тривиально разрушаемы.
В моем (скромном) опыте я никогда не видел разницы с точки зрения кода, сгенерированного компилятором, между:
- класс с тривиальным значением по умолчанию ctor и/или тривиальным dtor и
- класс с определяемым пользователем пустым ctor и/или не виртуальным пользователем пустым dtor (если класс, его базовые классы и классы участников также имеют не виртуальный dtor пользователь, определенный пустым или тривиальным)
Итак, мои вопросы:
- Каким образом пользователь может определить пустой ctor/dtor, может или не может считаться тривиальным ctor/dtor относительно генерации кода компилятора, оптимизации, компромиссов,...
- Тот же вопрос с пользовательским непустым ctor/dtor; какие правила должны следовать коду, реализованному в ctor/dtor, чтобы рассматривать их как тривиальные.
Мой вопрос не связан со стандартом (пожалуйста, не отвечайте стандартным состояниям, что является тривиальным ctor/dtor, поэтому пользовательский ctor/dtor не является), а в том, как компиляторы имеют дело с определенными пользователем ctor/dtor и в каким образом поведение скомпилированного кода может измениться (или нет) по сравнению с тривиальным ctor/dtor.
Ответы
Ответ 1
Каким образом пользователь может определить пустой ctor/dtor, может или не может считаться тривиальным ctor/dtor относительно генерации кода компилятора, оптимизации, компромиссов,...
Если конструктор/деструктор не встроены, компилятор может (в зависимости от оптимизации времени ссылки) вызывать вызов для них, даже если они не являются операциями.
Например, следующий код:
struct Struct {
Struct();
~Struct();
};
int main() {
Struct s;
}
Скомпилирован (с включенными оптимизациями):
main:
subq $24, %rsp
leaq 15(%rsp), %rdi
call Struct::Struct()
leaq 15(%rsp), %rdi
call Struct::~Struct()
xorl %eax, %eax
addq $24, %rsp
ret
Обратите внимание, что все еще есть вызов конструктора и деструктора, хотя в отдельном файле я мог бы определить их как пустые функции.
Если, однако, вы ввели определения:
struct Struct {
Struct() {}
~Struct() {}
};
Struct foo() {
return Struct{};
}
Тогда компилятор может (и будет, если он не полностью сосать) обрабатывать их так же, как тривиальные конструкторы/деструкторы:
foo():
movq %rdi, %rax
ret
В этом примере любые вызовы конструктора/деструктора полностью оптимизированы, а сгенерированный код такой же, как если бы определение Struct
было простым struct Struct {};
.
Тот же вопрос с заданным пользователем непустым ctor/dtor; какие правила должны следовать коду, реализованному в ctor/dtor, чтобы рассматривать их как тривиальные.
От этого зависит. Опять же, если конструктор/деструктор не встроены, компилятору все равно придется вызывать обращения к ним, и в этом случае они вовсе не тривиальны.
Однако встроенные непустые конструкторы/деструкторы могут по-прежнему быть "тривиальными", если оптимизатор может полностью их оптимизировать (например, если они содержат только for (int x = 0; x < 1000; ++x);
, то это бесполезный код, который можно оптимизировать ) до такой степени, что они эффективно пустые.
Но если они делают полезную работу, которую нельзя просто оптимизировать, тогда они не будут вообще тривиальными. Они будут работать. Они должны.
Ответ 2
Вы знаете стандарты лучше, чем я, но, основываясь на информации, которую вы предоставили, стандарт определяет тривиальный деструктор, но он не определяет пустой деструктор, что сделало бы этот вопрос неправильным. Тривиальный деструктор - это особый случай, который компиляторы могут оптимизировать, и хотя пустой конструктор имеет для нас смысл, это не то, что нужно учитывать разработчикам компилятора.
Просмотр нескольких ссылок SO:
- Почему пользовательские деструкторы, определенные пользователем по умолчанию на С++, увеличивает время выполнения? показывает случай, когда компилятор действует по-разному для тривиальных и пустых деструкторов. Ответ там подразумевает одно отличие в обработке исключений. Он не ищет пустой конструктор, потому что ему не требуется, и поэтому обрабатывает исключения, как если бы в dtor был допустимый код.
- Будет ли "пустой" конструктор или деструктор сделать то же самое, что и сгенерированный?, кажется, настолько близко соответствует вашему вопросу, что это может быть дубликат, Лучше читать его самостоятельно, вместо того чтобы полагаться на мою интерпретацию, но в нем упоминаются компиляторы Microsoft, которые не могут внедрять пустые деструкторы, и все компиляторы, желающие работать с деструктором рабочего класса (и это очень плохая практика программирования для base dtor не будет виртуальным).
Чтобы ответить на второй вопрос, как только ваш ctor не пуст, это не тривиально. Ближе всего вы добираетесь до тривиального - это пустой ctor/dtor, и ваше тщательное прочтение стандарта уже говорит вам, что это не определено как тривиальное.
TL; DR: Стандарт определяет тривиальный dtor, но не пустой. Смарт-компиляторы могут заметить, что он определен пользователем пустым и рассматривает его как тривиальный, но стандарт не требует такого рассмотрения.