Странное перечисление в деструкторе
В настоящее время я читаю исходный код Protocol Buffer
, и я нашел один странный код enum
, определенный здесь
~scoped_ptr() {
enum { type_must_be_complete = sizeof(C) };
delete ptr_;
}
void reset(C* p = NULL) {
if (p != ptr_) {
enum { type_must_be_complete = sizeof(C) };
delete ptr_;
ptr_ = p;
}
}
Почему здесь указан enum { type_must_be_complete = sizeof(C) };
? для чего он используется?
Ответы
Ответ 1
Этот трюк позволяет избежать UB, гарантируя, что определение C доступно при компиляции этого деструктора. В противном случае компиляция завершится неудачно, поскольку неполный тип sizeof
(пересылаемые объявленные типы) не может быть определен, но можно использовать указатели.
В скомпилированном двоичном коде этот код будет оптимизирован и не будет иметь никакого эффекта.
Обратите внимание: Удаление неполного типа может быть undefined поведение из 5.3.5/5:.
если удаляемый объект имеет тип неполный класс в точке удаление, а полный класс имеет нетривиальный деструктор или deallocation, поведение undefined.
g++
даже выдает следующее предупреждение:
предупреждение: возможная проблема, обнаруженная при вызове delete оператор:
предупреждение: 'p' имеет недопустимый тип
предупреждение: вперед объявление 'struct C'
Ответ 2
sizeof(C)
завершится с ошибкой во время компиляции, если C
не является полным типом. Установка локальной области enum
в нее делает утверждение доброкачественным во время выполнения.
Это способ программиста, защищающий себя от себя: поведение последующего delete ptr_
на неполном типе undefined, если оно имеет нетривиальный деструктор.
Ответ 3
Чтобы понять enum
, начните с рассмотрения деструктора без него:
~scoped_ptr() {
delete ptr_;
}
где ptr_
- C*
. Если тип C
является неполным в этой точке, то есть все, что знает компилятор, это struct C;
, тогда (1) созданный по умолчанию деструктор do-nothing используется для указанного экземпляра C. Это вряд ли будет правильным для объекта, управляемого умным указателем.
Если удаление с помощью указателя на неполный тип всегда имело Undefined Behavior, то стандарт мог просто потребовать, чтобы компилятор его диагностировал и завершил сбой. Но он четко определен, когда реальный деструктор тривиален: знание, которое может иметь программист, но компилятор его не имеет. Почему язык определяет и позволяет это превыше меня, но С++ поддерживает многие практики, которые сегодня не рассматриваются как лучшие практики.
Полный тип имеет известный размер, и, следовательно, sizeof(C)
будет компилироваться тогда и только тогда, когда C
является полным типом - с известным деструктором. Поэтому его можно использовать в качестве охранника. Один из способов - просто
(void) sizeof(C); // Type must be complete
Я бы предположил, что с некоторыми компиляторами и параметрами компилятор оптимизирует его, прежде чем он сможет заметить, что он не должен компилироваться и что enum
- это способ избежать такого несоответствующего поведения компилятора:
enum { type_must_be_complete = sizeof(C) };
Альтернативное объяснение выбора enum
, а не просто отброшенное выражение, является просто личным предпочтением.
Или как Джеймс Т. Хаггет предлагает в комментарии к этому ответу: "Перечисление может быть способом создания псевдопортативного сообщения об ошибке во время компиляции".
(1) Созданный по умолчанию деструктор do-nothing для неполного типа был проблемой со старым std::auto_ptr
. Это было настолько коварно, что он пробрался в элемент GOTW о идиоме PIMPL, написанный председателем международного комитета по стандартизации С++ Херб Саттер, Конечно, в настоящее время std::auto_ptr
устарел, вместо этого будет использоваться какой-то другой механизм.
Ответ 4
Может быть, трюк, чтобы быть уверенным, что C
определен.