Странное перечисление в деструкторе

83

В настоящее время я читаю исходный код 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) };определяется здесь? для чего это используется?

Zangw
источник
2
Если я хочу быть в этом уверен, я лучше буду использовать ptr_себя в sizeofas sizeof(*ptr_)вместо sizeof(C).
Nawaz

Ответы:

81

Этот трюк позволяет избежать UB, гарантируя, что определение C доступно при компиляции этого деструктора. В противном случае компиляция не удалась бы, поскольку sizeofнеполный тип (объявленные вперед типы) нельзя определить, но можно использовать указатели.

В скомпилированном двоичном коде этот код будет оптимизирован и не будет иметь никакого эффекта.

Обратите внимание, что: Удаление неполного типа может быть неопределенным поведением из 5.3.5 / 5 :.

если удаляемый объект имеет неполный тип класса на момент удаления, а полный класс имеет нетривиальный деструктор или функцию освобождения, поведение не определено .

g++ даже выдает следующее предупреждение:

предупреждение: возможная проблема обнаружена при вызове оператора удаления:
предупреждение: 'p' имеет неполный тип
предупреждение: прямое объявление 'struct C'

Мохит Джайн
источник
1
«Удаление неполного типа - неопределенное поведение» неверно. Это только UB, если тип имеет нетривиальный деструктор. Проблема, которую решает этот небольшой трюк, заключается в том, что удаление неполного типа не всегда является UB, поэтому язык его поддерживает.
Приветствия и hth. - Alf
Спасибо @ Cheersandhth.-Alf Я хотел сказать, что это может быть UB, так что в целом эта строка кода - UB. Отредактировано.
Mohit Jain
32

sizeof(C)завершится ошибкой во время компиляции, если Cэто не полный тип. Установка локальной области видимости enumделает оператор безопасным во время выполнения.

Это способ программиста защитить себя от самих себя: поведение последующего delete ptr_на неполном типе не определено, если у него есть нетривиальный деструктор.

Вирсавия
источник
1
Можете ли вы объяснить, почему C должен быть полным типом на этом этапе - необходимо ли иметь полное определение типа для его вызова delete? И если да, то почему компилятор все равно его не улавливает?
Питер Халл
1
Разве это не об избегании C = void? Если бы это Cбыл просто неопределенный тип, разве deleteоператор не завершился бы ошибкой?
Kerrek SB
1
Похоже, у Мохита Джайна есть ответ.
Питер Халл
1
−1 «Установка для него перечисления локальной области делает оператор безопасным во время выполнения». бессмысленно. Мне жаль.
Приветствия и hth. - Альф,
1
@SteveJessop, спасибо. Часть, которую мне не хватало, заключалась в том, что удаление неполного типа - это UB.
Питер Халл
28

Чтобы понять это enum, начните с рассмотрения деструктора без него:

~scoped_ptr() {
    delete ptr_;
}

где ptr_а C*. Если тип Cна этом этапе является неполным, то есть все, что известно компилятору struct C;, то (1) для указанного экземпляра C используется деструктор, созданный по умолчанию . Вряд ли это будет правильным решением для объекта, управляемого интеллектуальным указателем.

Если удаление с помощью указателя на неполный тип всегда имело неопределенное поведение, тогда стандарт мог бы просто потребовать, чтобы компилятор диагностировал его и завершился ошибкой. Но это четко определено, когда реальный деструктор тривиален: знания, которые может иметь программист, но нет у компилятора. Я не понимаю, почему язык определяет и допускает это, но C ++ поддерживает многие практики, которые сегодня не считаются лучшими.

Полный тип имеет известный размер и, следовательно, sizeof(C)будет компилироваться тогда и только тогда, когда Cэто полный тип - с известным деструктором. Так что его можно использовать как охранник. Один путь был бы просто

(void) sizeof(C);  // Type must be complete

Я предполагаю, что с некоторым компилятором и параметрами компилятор оптимизирует его, прежде чем он сможет заметить, что он не должен компилироваться, и что enumэто способ избежать такого несоответствующего поведения компилятора:

enum { type_must_be_complete = sizeof(C) };

Альтернативное объяснение выбора, enumа не просто отброшенное выражение, - это просто личные предпочтения.

Или, как предлагает Джеймс Т. Хаггет в комментарии к этому ответу, «перечисление может быть способом создания псевдопереносимого сообщения об ошибке во время компиляции».


(1) Сгенерированный по умолчанию деструктор бездействия для неполного типа был проблемой со старым std::auto_ptr. Он был настолько коварен, что попал в статью GOTW об идиоме PIMPL , написанную председателем международного комитета по стандартизации C ++ Хербом Саттером. Конечно, в настоящее время это std::auto_ptrне рекомендуется, вместо этого можно использовать какой-то другой механизм.

Приветствия и hth. - Альф
источник
4
Перечисление может быть способом создания псевдопереносимого сообщения об ошибке во время компиляции.
Брайс М. Демпси
1
Я думаю, что этот ответ очень хорошо объясняет мотивацию кода, но я хотел бы добавить, что (1) некоторые компиляторы имеют sizeof(T)оценку 0 для неполных типов вместо сбоя компиляции. Однако это некорректное поведение. (2) Начиная с C ++ 11, использование static_assert((sizeof(T) > 0), "T must be a complete type");было бы превосходным (и идиоматическим) решением.
5gon12eder
@ 5gon12eder; Приведите пример компилятора C ++, у которого « sizeof(T)оценка для неполных типов равна 0».
Приветствия и hth. - Alf
1
Я никогда не использовал такой компилятор и не удивлюсь, если узнаю, что они уже вымерли. И даже если они этого не сделали, не заботиться о несоответствующей реализации - правильное решение. Тем не менее, как libstdc ++, так и libc ++ используют static_assert(sizeof(T) > 0, "…");в своих соответствующих реализациях, std::unique_ptrчтобы убедиться, что тип является полным…
5gon12eder
1
… Так что я думаю, что можно с уверенностью сказать, что это идиоматика. В любом случае, поскольку оценка sizeof(T)в логическом контексте в точности эквивалентна тестированию sizeof(T) > 0, на самом деле это не имеет значения, за исключением, возможно, эстетических соображений.
5gon12eder
3

Может быть, уловка Cопределена.

Джером
источник