Какая свобода компилятора для устранения деструктора?
Хорошо известно, что при определенных условиях компилятор может вызвать вызовы конструктора копирования. Однако в Стандарте ясно сказано, что компилятор имеет право изменять поведение во время выполнения (вызов или не конструктор копирования), но перевод выполняется так, как будто вызывается конструктор копирования. В частности, компилятор проверяет, есть ли допустимый конструктор копирования для вызова.
Я столкнулся с ситуацией, когда вызов деструктора может быть устранен, но компиляторы отличаются тем, должен ли существовать действительный деструктор или нет.
Вот полный пример, показывающий, как эта проблема может возникнуть и как компиляторы отличаются поведением.
template <typename T>
struct A {
~A() { (void) sizeof(T); }
};
struct B; // defined elsewhere.
struct C {
A<B> x, y;
~C(); // defined in a TU where B is complete.
};
int main() {
C c;
}
При компиляции main()
компилятор генерирует конструктор по умолчанию C
. Этот конструктор по умолчанию инициализирует сначала x
, а затем y
. Если во время построения y
возникает исключение, то x
необходимо уничтожить. Сгенерированный код выглядит следующим образом:
new ((void*) &this->x) A<B>; // default initializes this->x.
try {
new ((void*) &this->y) A<B>; // default initializes this->y.
}
catch (...) {
(this->x).~A<B>(); // destroys this->x.
throw;
}
Зная, что конструктор по умолчанию A<B>
тривиален (и не бросает), в соответствии с правилом as-if компилятор может упростить код:
new ((void*) &this->x) A<B>; // default initializes this->x.
new ((void*) &this->y) A<B>; // default initializes this->y.
Поэтому нет необходимости вызывать ~A<B>()
. (На самом деле, компилятор может даже удалить две инициализации выше, поскольку конструктор A<B>
тривиален, но это не важно для этого обсуждения.)
Вопрос: Несмотря на то, что вызов деструктора может быть отменен, должен ли компилятор проверить, доступен ли действительный деструктор? Я не смог найти что-либо в Стандарте, который прояснил бы этот вопрос. Может ли кто-либо предоставлять соответствующие кавычки?
Если компилятор решает не переводить ~A<B>()
(например, gcc и Visual Studio do), компиляция завершается успешно.
Однако, если компилятор решает перевести ~A<B>()
в любом случае (например, clang и icc do), тогда возникает ошибка, потому что здесь B
является неполным типом, и его размер не может быть выполнен.
Ответы
Ответ 1
Я не думаю, что это указано стандартом. Если ~A<B>
создается, то он плохо сформирован и требуется диагностика. И, как вы говорите, если построение y
бросает, то x
должен быть уничтожен.
Однако построение y
никогда не может быть брошено, поэтому, возможно, никогда не будет требования к определению деструктора (15.2/2, 14.7.1/3).