Дивний перелік у деструкторі


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) };тут визначено? для чого він використовується?


2
Якщо я хочу бути впевненим у цьому, то я б скоріше використовував ptr_себе sizeofяк, sizeof(*ptr_)а не як sizeof(C).
Наваз,

Відповіді:


81

Цей фокус дозволяє уникнути UB, забезпечуючи доступність визначення C, коли цей деструктор компілюється. В іншому випадку компіляція зазнає невдачі, оскільки sizeofнеповний тип (задекларовані типи) неможливо визначити, але можна використовувати покажчики.

У скомпільованому двійковому коді цей код буде оптимізований і не матиме ефекту.

Зауважте, що: Видалення неповного типу може бути невизначеною поведінкою з 5.3.5 / 5 :.

якщо об'єкт, що видаляється, має неповний тип класу в точці видалення, а повний клас має нетривіальний деструктор або функцію вивільнення, поведінка невизначена .

g++ навіть видає таке попередження:

попередження: виявлена ​​можлива проблема при виклику оператора видалення:
попередження: 'p' має неповний тип
попередження: пряме оголошення про 'struct C'


1
"Видалення неповного типу - невизначена поведінка" - неправильно. Це лише UB, якщо тип має нетривіальний деструктор. Проблема, яку вирішує цей маленький фокус, полягає саме в тому, що видалення неповного типу не завжди є UB, так що мова підтримує це.
Вітаю і hth. - Альф

Дякую @ Cheersandhth.-Alf. Я міркував, що це може бути UB, тому загалом цей рядок коду UB. Відредаговано.
Мохіт Джейн

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_a 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 , написаний головою міжнародного комітету стандартизації С ++ Херб Саттер. Звичайно, сьогодні, коли std::auto_ptrце застаріло, замість цього буде використовуватися інший механізм.


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. - Альф

1
Я ніколи не користувався таким компілятором, і я б не здивувався, почувши, що вони на сьогодні вимерли. І навіть якщо вони цього не зробили, не піклуватися про невідповідне імплементацію є дійсним рішенням. Тим не менше, як libstdc ++, так і libc ++ використовують static_assert(sizeof(T) > 0, "…");у своїх відповідних реалізаціях, std::unique_ptrщоб переконатися, що тип завершений ...
5gon12eder

1
... тому я думаю, що можна з упевненістю сказати, що це ідіоматично. У будь-якому випадку, оскільки оцінка sizeof(T)в логічному контексті точно еквівалентна тестуванню sizeof(T) > 0, це насправді не має значення, крім, можливо, з естетичних причин.
5gon12eder

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.