Наблюдаемое поведение и поведение undefined. Что произойдет, если я не вызову деструктор?

Примечание. Я видел похожие вопросы, но ни один из ответов не достаточно точен, поэтому я сам прошу об этом.

Это очень нелепый вопрос "язык-юрист"; Я ищу авторитетный ответ.

В стандарте С++ говорится:

Программа может завершить время жизни любого объекта, повторно используя хранилище, которое занимает объект, или явно вызвав деструктор для объекта типа класса с нетривиальным деструктором. Для объекта типа класса с нетривиальным деструктором программа не требует прямого вызова деструктора до того, как хранилище, которое объект занимает, повторно используется или освобождается; однако, если нет явного вызова деструктора или если выражение удаления не используется для освобождения хранилища, деструктор не должен быть неявно назван , и любая программа, зависящая от побочных эффектов, создаваемых деструктором, не определена поведение.

Я просто не понимаю, что означает "зависит от побочных эффектов".

Общий вопрос:

Забывает ли вызвать деструктор, отличный от забывания вызвать обычную функцию с тем же самым телом?

Конкретный пример для иллюстрации моей точки:

Рассмотрим такую ​​программу, как показано ниже. Также рассмотрите очевидные варианты (например, что, если я не создаю объект поверх другого, но я все равно забыл вызвать деструктор, что, если я не распечатываю вывод для его наблюдения и т.д.):

#include <math.h>
#include <stdio.h>

struct MakeRandom
{
    int *p;
    MakeRandom(int *p) : p(p) { *p = rand(); }
    ~MakeRandom() { *p ^= rand(); }
};

int main()
{
    srand((unsigned) time(NULL));        // Set a random seed... not so important
    // In C++11 we could use std::random_xyz instead, that not the point

    int x = 0;
    MakeRandom *r = new MakeRandom(&x);  // Oops, forgot to call the destructor
    new (r) MakeRandom(&x);              // Heck, I'll make another object on top
    r->~MakeRandom();                    // I'll remember to destroy this one!
    printf("%d", x);                     // ... so is this undefined behavior!?!
    // If it indeed UB: now what if I didn't print anything?
}

Мне кажется смешным сказать, что это показывает "поведение undefined", потому что x уже является случайным - и поэтому XORing это другое случайное число не может сделать программу более "undefined", чем раньше, может это?

Кроме того, в какой момент правильно сказать, что программа "зависит" от деструктора? Делает ли это так, если значение было случайным - или вообще, если мне не удавалось отличить деструктор от работы или не работает? Что, если я никогда не прочитаю эту ценность? В основном:

При каких условиях (s), если таковые имеются, показывает ли эта программа Undefined Поведение?

Точно, какое выражение (выражения) или оператор вызывает это и почему?

Ответ 1

Я просто не понимаю, что означает "зависит от побочных эффектов".

Это означает, что это зависит от того, что делает деструктор. В вашем примере, изменив *p или не изменив его. У вас есть эта зависимость в вашем коде, поскольку результат будет отличаться, если dctor не будет вызван.

В вашем текущем коде число, которое напечатано, может не совпадать с номером, который был бы возвращен вторым вызовом rand(). Ваша программа вызывает поведение undefined, но только то, что UB здесь не имеет вредного эффекта.

Если вы не будете печатать значение (или иначе читаете его), тогда не будет никакой зависимости от побочных эффектов dcor и, следовательно, нет UB.

Итак:

Забывает ли вы назвать деструктор иначе, чем забыть вызвать обычную функцию с тем же телом?

Нет, в этом нет ничего особенного. Если вы зависите от того, что он вызван, вы должны убедиться, что он вызван, иначе ваша зависимость не будет удовлетворена.

Кроме того, в какой момент правильно сказать, что программа "зависит" от деструктора? Делает ли это так, если значение было случайным - или вообще, если мне не удавалось отличить деструктор от работы и не работать?

Случайный или нет, не имеет значения, потому что код зависит от переменной, на которую записывается. Просто потому, что трудно предсказать, что новое значение не означает, что нет зависимости.

Что делать, если я никогда не читаю значение?

Тогда нет UB, поскольку код не имеет зависимости от переменной после того, как она была записана.

При каких условиях (s), если таковые имеются, показывает ли эта программа undefined Поведение?

Нет никаких условий. Это всегда UB.

Точно, какое выражение (выражения) или оператор вызывает это и почему?

Выражение:

printf("%d", x);

поскольку он вводит зависимость от затронутой переменной.

Ответ 2

Это имеет смысл, если вы согласны с тем, что Стандарт требует, чтобы распределение было сбалансировано путем уничтожения в случае, когда деструкторы влияют на поведение программы. То есть единственная правдоподобная интерпретация заключается в том, что если программа

  • когда-либо не удается вызвать деструктор (возможно, косвенно через delete) для объекта и
  • указанный деструктор имеет побочные эффекты,

то программа обречена на землю UB. (OTOH, если деструктор не влияет на поведение программы, тогда вы отключены. Вы можете пропустить вызов.)

Примечание добавлено Побочные эффекты обсуждаются в этой статье SO, и я не буду здесь повторять. Консервативный вывод состоит в том, что "программа... зависит от деструктора" эквивалентна "деструктор имеет побочный эффект".

Дополнительная заметка Однако стандарт, по-видимому, допускает более либеральную интерпретацию. Он формально не определяет зависимость программы. (Он определяет конкретное качество выражений как несущую зависимость, но это здесь не применяется.) Однако в более чем 100 случаях использования производных от "A зависит от B" и "A имеет зависимость от B", в нем используется обычное смысл слова: вариация В ведет непосредственно к вариации в А. Следовательно, не кажется скачком, чтобы сделать вывод о том, что программа Р зависит от побочного эффекта Е в той мере, в которой производительность или невыполнение результатов Е в вариации в наблюдаемом поведении во время выполнения П. Здесь мы находимся на твердой почве. Значение программы - ее семантика - эквивалентно стандарту его наблюдаемому поведению во время исполнения, и это четко определено.

Наименьшие требования к соответствующей реализации:

  • Доступ к изменчивым объектам оценивается строго в соответствии с правилами абстрактной машины.

  • При завершении программы все данные, записанные в файлы, должны быть идентичны одному из возможных результатов выполнения программы в соответствии с абстрактной семантикой.

  • Динамика входа и выхода интерактивных устройств должна проходить таким образом, чтобы вывод запроса фактически доставляется до того, как программа ждет ввода. Что представляет собой интерактивное устройство определено реализацией.

Эти совокупности называются наблюдаемым поведением программы.

Таким образом, в соответствии со стандартными соглашениями, если побочный эффект деструктора в конечном итоге повлияет на доступ к данным, вход или выход во вкладном хранилище и что деструктор никогда не вызывается, программа имеет UB.

Поставьте еще один способ: если ваши деструкторы выполняют значимые вещи и не вызывают постоянной последовательности, ваша программа (говорит стандарт) должна быть рассмотрена и объявлена ​​бесполезной.

Является ли это чрезмерно ограничительным, но педантичным, для языкового стандарта? (В конце концов, стандарт предотвращает появление побочного эффекта из-за неявного вызова деструктора, а затем вызывает вас, если деструктор вызвал бы изменение наблюдаемого поведения, если бы оно было вызвано!) Возможно, так. Но это имеет смысл как способ настаивать на хорошо сформированных программах.

Ответ 3

Это действительно не очень хорошо определенная вещь в стандарте, но я бы интерпретировал "зависит от" как означающее "поведение в соответствии с правилами абстрактной машины".

Это поведение состоит из последовательности чтения и записи в изменчивые переменные и вызовы функций библиотечного ввода-вывода (который включает в себя, по крайней мере, функции ввода-вывода стандартной библиотеки, например printf, но может также включать любое число дополнительных функций в любой заданной реализации, например, функции WinAPI). См. 1.9/9 для точной формулировки.

Таким образом, поведение undefined, если выполнение деструктора или его отсутствие влияет на это поведение. В вашем примере, будет ли деструктор выполнен или не влияет на значение x, но это хранилище все равно мертво, так как следующий вызов конструктора перезаписывает его, поэтому компилятор может фактически оптимизировать его (и, скорее всего, это произойдет), Но что более важно, вызов rand() влияет на внутреннее состояние RNG, что влияет на значения, возвращаемые rand() в другом объектном конструкторе и деструкторе, поэтому он влияет на конечное значение x. Это "случайный" (псевдослучайный) в любом случае, но это будет другое значение. Затем вы печатаете x, превращая эту модификацию в наблюдаемое поведение, тем самым делая программу undefined.

Если вы никогда ничего не наблюдали с x или состоянием RNG, наблюдаемое поведение было бы неизменным независимо от того, вызван ли деструктор или нет, поэтому он не будет undefined.

Ответ 4

Для этого ответа я буду использовать версию С++ 11 на С++, которая может быть найдена здесь (стандарт С++), потому что это свободно доступно и обновляется.

Следующие три слова, используемые в вашем вопросе, выполняются следующим образом:

  • Destructor - 385 раз
  • Побочный эффект - 71 раз
  • Зависит - 41 раз

Печально "зависит от побочного эффекта" появляется только один раз, а DEPENDS ON не является стандартным идентификатором RFC, например SHALL, поэтому довольно сложно определить, что зависит от средств.

Зависит от

Возьмем подход "активистского судьи" и предположим, что "зависит", "зависимость" и "зависящее" все используются в аналогичном контексте в этом документе, то есть, этот язык использовался для передачи широкого идея, а не передать концепцию legalease.

Затем мы можем проанализировать эту часть страницы 1194:

17.6.3.2
E ff ect по оригинальной функции: обмен функцией перемещен в разный заголовок
Обоснование: удалить зависимость для обмена.
E ff ect по оригинальной функции: действительный код С++ 2003, который был скомпилирован, ожидая своп, чтобы быть в < алгоритм > возможно, должен включать < утилитa > .

Эта часть указывает на строгую зависимость; вам сначала нужно было включить std:: swap. "зависит от" , следовательно, указывает на строгое требование, необходимость в том, чтобы говорить, в том смысле, что нет достаточного контекста без требования действовать; отказ будет происходить без зависимости.

Я выбрал этот отрывок, потому что он максимально ясно передает намеченное значение; другие отрывки более подробные, но все они включают в себя аналогичный смысл: необходимость.

Следовательно, отношение "зависит от" означает, что зависящая от вещи вещь необходима для того, чтобы зависящий элемент имел смысл, был целым и полным и мог использоваться в контексте.

Чтобы разрезать эту легальную волокиту, это означает, что A зависит от B означает, что A требует B. Это в основном то, что вы понимаете "зависеть", чтобы означать, если вы посмотрели его в словаре или произнесли в предложении.

Побочный эффект

Это более строго определено на стр. 10:

Доступ к объекту, обозначенному изменчивым значением glvalue (3.10), изменение объекта, вызов библиотеки ввода-вывода функция или вызов функции, которая выполняет любую из этих операций, - это все побочные эффекты, , которые являются изменениями в состояние среды выполнения.

Это означает, что все, что приводит к изменению среды (например, ОЗУ, сетевому IO, переменным и т.д. и т.д.), являются побочными эффектами. Это аккуратно соответствует понятию примеси/чистоты с функциональных языков, что явно означает, что было предназначено. Обратите внимание, что стандарт С++ не требует наблюдения таких побочных эффектов; модификация переменной каким-либо образом, даже если эта переменная никогда не просматривается, по-прежнему является побочным эффектом.

Однако из-за правила "как будто" такие ненаблюдаемые побочные эффекты могут быть удалены, стр. 8:

Соответствующая реализация, выполняющая хорошо сформированную программу, должна обеспечивать такое же наблюдаемое поведение как одно из возможных исполнений соответствующего экземпляра абстрактной машины с той же программой и тот же ввод. Однако, если какое-либо такое исполнение содержит неиспользуемую операцию, этот Международный Стандартные места не требуют выполнения этой программы с этим вводом (даже не в отношении операций, предшествующих первой неработающей операции).

Зависит от побочных эффектов

Объединяя эти два определения вместе, мы можем теперь определить эту фразу: что-то зависит от побочных эффектов, когда эти изменения в среде исполнения требуются для удовлетворения целенаправленных, целых и полных операций программы. Если без побочных эффектов не выполняется какое-либо ограничение, которое требуется для работы программы стандартным образом, мы можем сказать, что она зависит от побочных эффектов.

Простым примером для иллюстрации этого может быть, как указано в другом ответе, блокировка. Программа, использующая блокировки , зависит от побочного эффекта блокировки, в частности, побочный эффект предоставления сериализованного шаблона доступа к некоторому ресурсу (упрощенному). Если этот побочный эффект нарушен, ограничения программы нарушаются, и, следовательно, программа не может восприниматься как чувственная (поскольку условия гонки или другие опасности могут возникать).

Программа DEPENDS на ограничениях, которые обеспечивает блокировка, посредством побочных эффектов; нарушая эти результаты в недействительной программе.

Зависит от побочных эффектов, создаваемых деструктором

Изменение языка от обращения к блокировке деструктору является простым и очевидным; если деструктор имеет побочные эффекты, которые удовлетворяют некоторому ограничению, которое требуется программе, чтобы быть точной, полной, полной и полезной, то это зависит от побочных эффектов, создаваемых деструктором. Это не совсем сложно понять и следует довольно легко из-за легальной интерпретации стандарта и беглого понимания понятием слов и того, как они используются.

Теперь мы можем ответить на ваши вопросы:

При каких условиях (s), если таковые имеются, показывает ли эта программа Undefined Поведение?

Всякий раз, когда зависимость или требование не выполняется, поскольку деструктор не вызывается, поведение любого зависимого кода undefined. Но что это значит?

1.3.24 неустановленное поведение
поведение, для которого настоящий международный стандарт не предъявляет требований

[Примечание. Невозможное поведение можно ожидать, если в этом международном стандарте отсутствует какое-либо явное определение поведение или когда программа использует ошибочную конструкцию или ошибочные данные.

Допустимое неопределенное поведение варьируется от полного игнорирования ситуации с непредсказуемыми результатами, поведения во время перевода или выполнение программы в документированном виде, характерном для окружающей среды (с выдачей или без диагностическое сообщение), до завершения перевода или выполнения (с выдачей диагностического сообщения).

Многие ошибочные программные конструкции не порождают неуверенное поведение; они должны быть диагностированы. - конечная нота]

Предположим на мгновение, что такое поведение определено.

Предположим, что это было явно незаконно. Тогда это потребует от любого стандартного компилятора обнаружить этот случай, чтобы его диагностировать, каким-то образом справиться с ним. Например, любой объект, который не был явно удален, должен быть удален при выходе программы, требуя какого-то механизма отслеживания и возможности выдавать деструкторы произвольным типам, возможно, неизвестным во время компиляции. Это в основном сборщик мусора, но при условии, что он может скрывать указатели, можно вызвать malloc и т.д. И т.д., Было бы существенно неосуществимо требовать этого.

Предположим, что это явно разрешено. Это также позволит компиляторам удалять вызовы деструкторов в соответствии с правилом as-if, так как, вы все равно не можете зависеть от этого поведения. Это приведет к некоторым неприятным сюрпризам, главным образом связанным с тем, что память не освобождается очень быстро или легко. Чтобы обойти это, мы все начнем использовать финализаторы, и проблема снова возникнет. Кроме того, разрешение этого поведения означает, что никакая библиотека не может быть уверенной, когда их память будет восстановлена ​​или когда она когда-либо будет, или если их блокировки, ресурсы, зависящие от ОС и т.д., Будут когда-либо возвращены. Это подталкивает требования к очистке от кода, используя ресурсы для кода, предоставляющего его, где это практически невозможно иметь дело на языке, таком как C или С++.

Предположим, что у него было определенное поведение; какое поведение это будет? Любое такое поведение должно было бы быть достаточно вовлеченным или не охватывало бы большое количество случаев. Мы уже рассмотрели два вопроса, и идея очистки любого объекта при выходе из программы накладывает большие накладные расходы. Для языка, который должен быть быстрым или, по крайней мере, минимальным, это явно лишнее бремя.

Поэтому вместо этого поведение было помечено как undefined, что означает, что любая реализация бесплатна для диагностики, но также позволяет просто игнорировать проблему и оставлять ее для выяснения. Но независимо от того, что, если вы зависите от тех ограничений, которые удовлетворяются, но не могут вызвать деструктор, вы получаете поведение Undefined. Даже если программа работает отлично, это поведение undefined; он может выдать сообщение об ошибке в какой-то новой версии Clang, он может удалить ваш жесткий диск в некоторых невероятно надежных криптографических ОС в будущем, он может работать до конца времени.

Но он все еще undefined.

Ваш пример

Ваш пример не удовлетворяет условию "зависит от" ; никакое ограничение, которое требуется для запуска программы, неудовлетворено.

  • Конструктор требует хорошо сформированного указателя на действительную переменную: выполнено
  • new требуется правильно выделенный буфер: удовлетворен
  • printf требует доступную переменную, интерпретируемую как целое: выполнено

Нет, где в этой программе определенное значение для x или отсутствие этого значения приводят к неудовлетворенности ограничения; вы не вызываете поведение Undefined. Ничто "не зависит" от этих побочных эффектов; если вы должны добавить тест, который функционировал как ограничение, требующее определенного значения для "x", тогда это будет поведение Undefined.

В его нынешнем виде ваш пример не соответствует Undefined; это просто неправильно.

Наконец-то!

Забывает ли вызвать деструктор, отличный от забывания вызвать обычную функцию с тем же телом?

Во многих случаях невозможно определить обычную функцию с одним и тем же телом:

  • Деструктор - это элемент, а не обычная функция
  • Функция не может получить доступ к закрытым или защищенным значениям.
  • Невозможно вызвать функцию при уничтожении
  • Финализатор также не может требовать вызова при уничтожении
  • Обычная функция не может восстановить память в ОС без вызова деструктора

И нет, вызов свободного на выделенный объект не может восстановить память; free/malloc не нужно работать над вещами, выделенными новыми, и без вызова деструктора частные члены данных не будут освобождены, что приведет к утечке памяти.

Кроме того, забыть о вызове функции не приведет к поведению Undefined, если ваша программа зависит от ее побочных эффектов; эти побочные эффекты просто не будут навязываться, и ваша программа не будет удовлетворять этим ограничениям и, вероятно, не будет работать должным образом. Однако забыть о вызове деструктора приводит к поведению Undefined, как указано на стр. 66:

Для объекта типа класса с нетривиальным деструктором, программа не требует прямого вызова деструктора до хранения который объект занимает, повторно используется или освобождается; однако, если нет явного вызова деструктора или если delete-expression (5.3.5) не используется для освобождения хранилища, деструктор не должен быть неявным образом вызван и любая программа, зависящая от побочных эффектов, создаваемых деструктором, имеет неопределенное поведение.

Как вы указали в своем исходном вопросе. Я не понимаю, почему вы должны были задать вопрос, учитывая, что вы уже ссылались на него, но там вы идете.

Ответ 5

В комментариях вы оставили простой вопрос, который заставил меня переосмыслить то, что я сказал. Я удалил старый ответ, потому что даже если бы он имел какое-то значение, это было далеко не так.

Итак, вы говорите, что мой код четко определен, так как он "не зависит от того, даже если я его распечатаю"? Нет undefined здесь?

Позвольте мне еще раз сказать, что я точно не помню определения правил размещения новых операторов и правил освобождения. На самом деле, я даже не прочитал новейший стандарт С++ полностью. Но если текст, который вы цитировали, оттуда, то вы попадаете в UB.

Не из-за Rand или Print. Или что-нибудь, что мы "видим".

Любой UB, который здесь происходит, состоит в том, что ваш код предполагает, что вы можете безопасно "перезаписать" старый "объект", не разрушая предыдущий экземпляр, который сидел в этом месте. Основной побочный эффект деструктора - это не "освобождение ручек/ресурсов" (который вы делаете вручную в своем коде!), Но оставляя пространство "готовым к повторному использованию/повторному использованию".

Вы предположили, что использование блоков памяти и времени жизни объектов не отслеживается. Я уверен, что стандарт С++ не определяет, что они не отслеживаются.

Например, представьте, что у вас есть тот же код, что и предоставленный, но этот класс struct/class имеет vtable. Представьте, что вы используете гиперприборный компилятор, у которого есть множество debugchecks, который с особой тщательностью управляет vtable и выделяет дополнительный битфлаг и который вводит код в базовые конструкторы и деструкторы, которые переворачивают этот флаг, чтобы помочь отслеживать ошибки. В таком компиляторе этот код разбился бы на строку new (r) MakeRandom, так как срок жизни первого объекта не был прерван. И я уверен, что такой придирчивый компилятор будет по-прежнему полностью совместим с С++, точно так же, как ваш компилятор тоже.

Это UB. Это только то, что большинство компиляторов действительно не делают таких проверок.

Ответ 6

Прежде всего, нам нужно определить поведение undefined, которое в соответствии с C часто задаваемым вопросом будет:

Все может произойти; Стандарт не предъявляет никаких требований. программа может не скомпилироваться или может выполняться неправильно (либо сбой или бесшумное генерирование неверных результатов), или он может случайным образом выполняет именно то, что планировал программист.

Что, другими словами, означает, что программист не может предсказать, что произойдет, как только программа будет выполнена. Это не означает, что программа или ОС будут разбиваться, это просто означает, что состояние будущего программы будет известно только после того, как оно будет выполнено.

Итак, объясняется в математической нотации, если программа сводится к функции F, которая превращает преобразование из начального состояния Is в конечное состояние Fs при заданных начальных условиях Ic


F (Is, Ic) → Fs


И если вы оцениваете функцию (выполнить программу) n раз, учитывая, что n- > ∞


F (Is, Ic) → Fs1, F (Is, Ic) → Fs2,..., F (Is, Ic) → Fsn, n- > ∞


Тогда:

  • A определенное поведение будет дано всеми результирующими состояниями, являющимися то же: Fs1 = Fs2 =... = Fsn, учитывая, что n- > ∞
  • Поведение undefined будет предоставлено возможностью получение разных законченных состояний между различными казнями. Fs1 ≠ Fs2 ≠... ≠ Fsn, учитывая, что n- > ∞

Обратите внимание, как я выделяю возможность, потому что поведение undefined - это именно то. Существует вероятность того, что программа выполняется по желанию, но ничто не гарантирует, что она это сделает, или что она не сделает этого.

Следовательно, отвечая на ваш ответ:

Забывает назвать деструктора иначе, чем забыть вызвать обычную функцию с тем же самым телом?

Учитывая, что деструктор - это функция, которая может быть вызвана даже тогда, когда вы не вызываете ее явно, забывая вызвать деструктор IS, отличный от забывания, чтобы вызвать обычной функции, и при этом МОЖЕТ привести к поведению undefined.

Обоснование объясняется тем фактом, что, когда вы забудете вызвать обычную функцию, вы УВЕРЕНЫ, заблаговременно, что эта функция не будет вызываться ни в какой точке вашей программы, даже когда вы запускаете свою программу бесконечное количество раз.

Однако, когда вы забыли вызвать деструктор, и вы вызываете свою программу бесконечно много раз, и, как показано в этом сообщении, fooobar.com/questions/93443/... при определенных обстоятельствах деструкторы С++ не вызываются, это означает, что вы не можете заверить заранее, когда деструктор будет вызван, или когда этого не будет. Эта неопределенность означает, что вы не можете гарантировать одно и то же конечное состояние, что приведет к UB.

Итак, отвечая на ваш второй вопрос:

При каком условии (s), если оно есть, показывает ли эта программа UndefinedПоведение?

Обстоятельства будут заданы обстоятельствами, когда деструкторы С++ не вызываются, указанными в ссылке, на которую я ссылался.

Ответ 7

Мое чтение этой части стандарта:

  • Вам разрешено повторно использовать хранилище для объекта, который имеет нетривиальный деструктор, не вызывая деструктор
  • Если вы это сделаете, компилятору не разрешено вызывать деструктор для вас.
  • Если ваша программа имеет логику, которая зависит от вызываемого деструктора, ваша программа может сломаться.

Побочные эффекты здесь - это просто изменения состояния программы, вызванные вызовом деструктора. Они будут такими, как обновление отсчетов ссылок, освобождение блокировок, закрытие дескрипторов и т.д.

"Зависит от побочных эффектов" означает, что другая часть программы ожидает, что счетчик ссылок будет поддерживаться правильно, блокировки будут выпущены, ручки закрыты и так далее. Если вы делаете практику отказа от деструкторов, вам нужно убедиться, что ваша программная логика не зависит от вызванных им вызовов.

Хотя "забыть" на самом деле не актуально, ответ - нет, деструкторы - это просто функции. Главное отличие заключается в том, что при некоторых обстоятельствах они вызываются компилятором ( "неявно" ), и этот раздел стандарта определяет ситуацию, в которой они не будут.

Ваш пример действительно не зависит от побочных эффектов. Он, очевидно, называет случайную функцию ровно 3 раза и печатает любое значение, которое он рассчитывает. Вы можете изменить его так:

  • Структура поддерживает счетчик ссылок (ctor +1, dtor -1)
  • Функция factory повторно использует объекты и случайно вызывает деструктор или нет
  • Клиентская функция "зависит от правильного ведения отсчета", ожидая, что она будет равна нулю.

Очевидно, что при этой зависимости программа будет показывать поведение undefined по отношению к счетчику ссылок.

Обратите внимание, что "undefined поведение" не должно быть плохим поведением. Это просто означает "поведение, для которого настоящий международный стандарт не предъявляет никаких требований".

Я действительно думаю, что есть опасность переосмыслить то, что принципиально довольно простое понятие. Я не могу процитировать никаких полномочий за пределами слов, которые здесь, и самого стандарта, который я нахожу совершенно ясным (но, во всяком случае, скажите мне, если я что-то упустил).

Ответ 8

Является ли программа "зависит от побочных эффектов, создаваемых деструктором", зависит от определения "наблюдаемого поведения".

Чтобы процитировать стандарт (раздел 1.9.8, выполнение программы добавлено жирным шрифтом):

Наименьшие требования к соответствующей реализации:

  • Доступ к неустойчивым объектам оценивается строго в соответствии с правилами абстрактной машины.
  • При завершении программы все данные, записанные в файлы, должны быть идентичны одному из возможных результатов выполнения программы в соответствии с абстрактной семантикой.
  • Динамика входных и выходных сигналов интерактивных устройств должна проходить таким образом, чтобы обеспечить фактическую доставку вывода перед тем, как программа ждет ввода. Что представляет собой интерактивный устройство реализовано.

Эти совокупности называются <сильным > наблюдаемым поведениемпрограмма. [Примечание: более строгие соответствия между абстрактными и фактическая семантика может определяться каждой реализацией. ]

Что касается вашего другого вопроса:

Забывает назвать деструктора иначе, чем забыть вызвать обычную функцию с тем же самым телом?

Да! Забытие "эквивалентного" вызова функции приводит к хорошо определенному поведению (как бы это ни предполагалось, что произошло, не происходит), но для деструктора это совершенно другое. По сути, в стандарте говорится, что если вы создадите свою программу так, чтобы наблюдаемый деструктор "забыли", то вы больше не пишете С++, а результат вашей программы полностью undefined.

Edit: О, верно, последний вопрос:

При каких условиях (s), если таковые имеются, показывает ли эта программа UndefinedПоведение?

Я считаю, что printf квалифицируется как запись в файл и поэтому является наблюдаемым. Конечно, rand() на самом деле не случайный, но полностью детерминирован для любого заданного семени, поэтому программа, написанная, демонстрирует поведение undefined (это говорит о том, что я был бы очень удивлен, если бы он не работал точно так, как написано, это просто не нужно).

Ответ 9

Я не читал всех остальных, но у меня есть простое объяснение. В цитате

однако, если нет явного вызова деструктора или если delete-expression не используется для освобождения хранилища, деструктор не должны быть неявно вызваны и любая программа, которая зависит от побочные эффекты, создаваемые деструктором, не подтвердили поведение.

Значение сильно отличается в зависимости от того, как вы его разбираете. Этот смысл - это то, о чем я говорю, люди говорят.

однако, {если нет явного вызова деструктора или если delete-expression не используется для освобождения хранилища}, деструктор не должны быть неявно вызваны и любая программа, которая зависит от побочные эффекты, создаваемые деструктором, не подтвердили поведение.

Но я думаю, что это значение имеет больше смысла

однако, если нет явного вызова деструктора или {if a delete-expression не используется для освобождения хранилища, деструктор не должны быть неявно вызваны и любая программа, которая зависит от побочные эффекты, создаваемые деструктором, не повлияли на поведение}.

который в основном говорит, что на С++ нет сборщика мусора, и если вы предполагаете он имеет GC, ваша программа не будет работать так, как вы ожидаете.

Ответ 10

В основном это означает, что когда вы определяете свой собственный деструктор для класса, он больше не вызывается автоматически при выходе из области. Объект по-прежнему будет недоступен, если вы попытаетесь его использовать, но память по-прежнему будет использоваться в стеке, и ничего в деструкторе, не используемом по умолчанию, не будет. Если вы хотите, чтобы количество объектов уменьшалось, когда вы вызываете своего деструктора, например, этого не произойдет.

Ответ 11

Стандарт должен говорить в терминах observable behavior и side effects, потому что, хотя многие люди часто забывают об этом, С++ не просто используется для программного обеспечения для ПК.

Рассмотрите пример в своем комментарии к ответу Гена:

class S { 
    unsigned char x; 
    public: ~S() { 
        ++x; 
    } 
};

деструктор здесь явно модифицирует объект - следовательно, a "побочный эффект" с данным определением - но я уверен, что нет программа может "зависеть" от этого побочного эффекта в любом разумном смысле срок. Что мне не хватает?

вам не хватает встроенного мира, например. Рассмотрим небольшую металлическую С++-программу, запущенную на маленьком процессоре с специальным доступом к регистру для доступа к uart:

new (address_of_uart_tx_special_function_register) S;

призывая деструктор, очевидно, наблюдаемые побочные эффекты. Если мы его не назовем, UART передает один байт меньше.

Следовательно, наблюдаемые побочные эффекты также зависят от того, что аппаратное обеспечение делает с записью в определенные ячейки памяти.

Можно также отметить, что даже если тело деструктора пуст, оно все равно может иметь побочные эффекты, если какая-либо из переменных-членов классов имеет деструкторы с побочными эффектами.

Я не вижу ничего, что запрещало бы компилятору выполнять другую бухгалтерскую работу (возможно, в отношении исключений и разворачивания стека). Даже если компилятор в настоящий момент не делает и никакой компилятор никогда не будет с точки зрения адвоката языка, вам все равно придется рассматривать его как UB, если вы не знаете, что компилятор не создает побочные эффекты.

Ответ 12

Именно эта проблема с формулировкой является предметом редакционного запроса на тягу к проекту стандарта C++ [basic.life] Удалить описание невозможного UB, который стремится исключить эту формулировку из проекта стандарта:

Деструктор, который не вызывается, не может вызывать побочные эффекты, поэтому невозможно зависеть от этих побочных эффектов.

После долгих обсуждений он, похоже, склонялся в этом направлении:

Редакционная встреча: Стандартные правила не могут зависеть от намерений программиста. Перейдите к CWG с целью применения как есть.

но это должно быть сначала рассмотрено основной рабочей группой (CWG) и поэтому не является редакционным изменением. Я полагаю, что это означает, что в конечном итоге будет отображаться в отчете о дефектах.

Таким образом, в заключение это выглядит как открытый вопрос о том, имеет ли эта формулировка какое-либо значение, но в конечном итоге она будет рассмотрена CWG.

Ответ 13

Скажите, что у вас есть класс, который получает блокировку в своем конструкторе, а затем освобождает блокировку в своем деструкторе. Освобождение блокировки является побочным эффектом вызова деструктора.

Теперь это ваша работа, чтобы обеспечить вызов деструктора. Обычно это делается при вызове delete, но вы также можете вызвать его напрямую, и это обычно делается, если вы выделили объект с использованием нового места размещения.

В вашем примере вы выделили 2 экземпляра MakeRandom, но только назвали деструктор на одном из них. Если бы это было управление некоторым ресурсом (например, файл), тогда у вас была бы утечка ресурса.

Итак, чтобы ответить на ваш вопрос, да, забыв позвонить деструктору, нужно забыть вызвать обычную функцию. Деструктор является обратным конструктору. Вы должны вызвать конструктор, и поэтому вам нужно вызвать деструктор, чтобы "размотать" все, что было сделано деструктором. Это не относится к "обычной" функции.