Жизнь после деструктора
От: Lis182 Россия  
Дата: 22.10.20 17:12
Оценка: :)
Добрый день! По моему опыту, тут много "формалистов" (в хорошем смысле) по поводу стандарта Си(++). Постараюсь максимально упростить вопрос, в реальности проблема немного сложнее. Приведем псевдокод, в котором собственно вопросы в комментариях.


struct SMyStruct // Простейшая структурка с одним полем
{
    int val;     // Поле, чтоб структура не была совсем пустой
    ~SMyStruct() // Деструктор
    {
        val = 321; // Что-то делаем (что именно не важно). Важно, что это действие можно повторять много раз, если под объект выделена память.
    }
};

{
    SMyStruct obj;    // выделяем память (обычно на стеке) под фактически один int
    obj.~SMyStruct(); // Явно вызываем деструктор
    obj.val = 123;    // Это правомерно? Т.е. указатель стека обязан не увеличиваться на siseof(int) после предыдущей строки?
}                     // Тут в любом случае вызовется деструктор, которому передастся this. Собственно, вопрос тот же, обязан ли компилятор держать в памяти объект до этого места
деструктор
Re: Жизнь после деструктора
От: SaZ  
Дата: 22.10.20 17:44
Оценка:
Здравствуйте, Lis182, Вы писали:

L>Добрый день! По моему опыту, тут много "формалистов" (в хорошем смысле) по поводу стандарта Си(++). Постараюсь максимально упростить вопрос, в реальности проблема немного сложнее. Приведем псевдокод, в котором собственно вопросы в комментариях.


L>

L>struct SMyStruct // Простейшая структурка с одним полем
L>{
L>    int val;     // Поле, чтоб структура не была совсем пустой
L>    ~SMyStruct() // Деструктор
L>    {
L>        val = 321; // Что-то делаем (что именно не важно). Важно, что это действие можно повторять много раз, если под объект выделена память.
L>    }
L>};

L>{
L>    SMyStruct obj;    // выделяем память (обычно на стеке) под фактически один int
L>    obj.~SMyStruct(); // Явно вызываем деструктор
L>    obj.val = 123;    // Это правомерно? Т.е. указатель стека обязан не увеличиваться на siseof(int) после предыдущей строки?
L>}                     // Тут в любом случае вызовется деструктор, которому передастся this. Собственно, вопрос тот же, обязан ли компилятор держать в памяти объект до этого места
L>


Да, память под ваш obj будет выделена на стеке и освобождена только при выходе из области видимости переменной. Но деструктор будет вызван дважды. Это очень хороший способ чтобы накосячить.
Я правильно понимаю, что вы пытаетесь прибить гвознями какой-то фикс вместо того чтобы правильно спроектировать класс?
Re: Жизнь после деструктора
От: a7d3  
Дата: 22.10.20 17:47
Оценка: +2
Здравствуйте, Lis182, Вы писали:

L>Добрый день! По моему опыту, тут много "формалистов" (в хорошем смысле) по поводу стандарта Си(++).


Формализм в данном случае предполагает, что есть некое формальное требование к компилятору, которому он должен соответствовать, дабы иметь заявленной поддержку некоего стандарта, а который данное требование входит.
Внимание вопрос, а в каком из стандартов сказано, что компилятор обязан отслеживать ручной вызов деструктора у объектов?
Второй вопрос, а исходя из чего с формальной точки зрения сделано заключение, что в приводимом примере экземпляр объекта должен будет существовать именно на стэке?
Re[2]: Жизнь после деструктора
От: Lis182 Россия  
Дата: 22.10.20 18:02
Оценка:
Здравствуйте, a7d3, Вы писали:

A>Здравствуйте, Lis182, Вы писали:


L>>Добрый день! По моему опыту, тут много "формалистов" (в хорошем смысле) по поводу стандарта Си(++).


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

A>Внимание вопрос, а в каком из стандартов сказано, что компилятор обязан отслеживать ручной вызов деструктора у объектов?
A>Второй вопрос, а исходя из чего с формальной точки зрения сделано заключение, что в приводимом примере экземпляр объекта должен будет существовать именно на стэке?

Деструкторов то отслеживать как раз не надо. Надо только гарантировать, что память не освободится, и в деструктор будет передаваться корректный this. На счет стека, то это сказано скорее образно. Понятно, что где и как выделена память касается только компилятора (или интерпретатора, который недавно писал).
Re[2]: Жизнь после деструктора
От: Lis182 Россия  
Дата: 22.10.20 18:14
Оценка:
Здравствуйте, SaZ, Вы писали:

SaZ>Здравствуйте, Lis182, Вы писали:


L>>Добрый день! По моему опыту, тут много "формалистов" (в хорошем смысле) по поводу стандарта Си(++). Постараюсь максимально упростить вопрос, в реальности проблема немного сложнее. Приведем псевдокод, в котором собственно вопросы в комментариях.


L>>

L>>struct SMyStruct // Простейшая структурка с одним полем
L>>{
L>>    int val;     // Поле, чтоб структура не была совсем пустой
L>>    ~SMyStruct() // Деструктор
L>>    {
L>>        val = 321; // Что-то делаем (что именно не важно). Важно, что это действие можно повторять много раз, если под объект выделена память.
L>>    }
L>>};

L>>{
L>>    SMyStruct obj;    // выделяем память (обычно на стеке) под фактически один int
L>>    obj.~SMyStruct(); // Явно вызываем деструктор
L>>    obj.val = 123;    // Это правомерно? Т.е. указатель стека обязан не увеличиваться на siseof(int) после предыдущей строки?
L>>}                     // Тут в любом случае вызовется деструктор, которому передастся this. Собственно, вопрос тот же, обязан ли компилятор держать в памяти объект до этого места
L>>


SaZ>Да, память под ваш obj будет выделена на стеке и освобождена только при выходе из области видимости переменной. Но деструктор будет вызван дважды. Это очень хороший способ чтобы накосячить.

SaZ>Я правильно понимаю, что вы пытаетесь прибить гвознями какой-то фикс вместо того чтобы правильно спроектировать класс?

Спасибо за ответ Не совсем прибить гвоздями. Это мой старый код. Деструктор был таким:


    inline ~SFile()
    {
        if (hFile != INVALID_HANDLE_VALUE)
            CloseHandle(hFile), hFile = INVALID_HANDLE_VALUE;
    }


Смысл в том, чтоб файл закрывался при выпрыгивания из функции в любом месте. Как не сложно видеть, файл закроется только если он не был закрыт. Но мне потребовалось закрывать этот файл посередине функции (потом этот файл переименовывался). Ну я и вставил вызов деструктора. Это компилилось и Студией и Интелом, и Gcc. Однако, при компиляции под Линуксом с максимальной оптимизацией начало падать. Вот я и хочу понять, я недопонимаю язык, или это ошибки компилятора.
Re[3]: Жизнь после деструктора
От: a7d3  
Дата: 22.10.20 18:19
Оценка:
Здравствуйте, Lis182, Вы писали:

L>Деструкторов то отслеживать как раз не надо. Надо только гарантировать, что память не освободится, и в деструктор будет передаваться корректный this. На счет стека, то это сказано скорее образно. Понятно, что где и как выделена память касается только компилятора (или интерпретатора, который недавно писал).


Тогда ещё раз, тоже самое, но другими словами.
Компиляция выполняется в результате многократного числа проходов по коду. На каждом из проходов что-то добавляется, что-то упрощается или выкидывается. Каждый проход использует результаты предыдущих. А теперь добавляем в код руками то самое, что могло или должно было быть добавлено в результате одного из таких проходов. Как именно тогда будет действовать компилятор?
Re[3]: Жизнь после деструктора
От: σ  
Дата: 22.10.20 18:28
Оценка:
L>Спасибо за ответ Не совсем прибить гвоздями. Это мой старый код. Деструктор был таким:

L>

L>    inline ~SFile()
L>    {
L>        if (hFile != INVALID_HANDLE_VALUE)
L>            CloseHandle(hFile), hFile = INVALID_HANDLE_VALUE;
L>    }

L>


L>Смысл в том, чтоб файл закрывался при выпрыгивания из функции в любом месте. Как не сложно видеть, файл закроется только если он не был закрыт. Но мне потребовалось закрывать этот файл посередине функции (потом этот файл переименовывался). Ну я и вставил вызов деструктора. Это компилилось и Студией и Интелом, и Gcc. Однако, при компиляции под Линуксом с максимальной оптимизацией начало падать. Вот я и хочу понять, я недопонимаю язык, или это ошибки компилятора.


Первое. Компилятор правомерно выкидывает бесполезное присваивание `hFile = INVALID_HANDLE_VALUE`.
Отредактировано 22.10.2020 18:48 σ . Предыдущая версия . Еще …
Отредактировано 22.10.2020 18:48 σ . Предыдущая версия .
Re[4]: Жизнь после деструктора
От: Lis182 Россия  
Дата: 22.10.20 18:32
Оценка:
A>Тогда ещё раз, тоже самое, но другими словами.
A>Компиляция выполняется в результате многократного числа проходов по коду. На каждом из проходов что-то добавляется, что-то упрощается или выкидывается. Каждый проход использует результаты предыдущих. А теперь добавляем в код руками то самое, что могло или должно было быть добавлено в результате одного из таких проходов. Как именно тогда будет действовать компилятор?

Спасибо за ответ Но деструктор это функция, и как любая функция должен вызываться если это указано в коде. У меня только сомнения в обязательности хранения объекта до выхода из области видимости. У меня этот код отлично работал с разными компиляторами много лет. А тут, под Линукс с полной оптимизацией стало падать. Причем вызовы то выполняются, только криво. Может быть this кривой, может таки стек очищается. Дизассемблировать, чтоб точно определить весьма сложно. Я хочу понять, проблема в поем недопонимании языка, или таки в ошибках компилятора.
Re[3]: Жизнь после деструктора
От: Pzz Россия https://github.com/alexpevzner
Дата: 22.10.20 18:35
Оценка: 1 (1) +4
Здравствуйте, Lis182, Вы писали:

L>Смысл в том, чтоб файл закрывался при выпрыгивания из функции в любом месте. Как не сложно видеть, файл закроется только если он не был закрыт. Но мне потребовалось закрывать этот файл посередине функции (потом этот файл переименовывался). Ну я и вставил вызов деструктора. Это компилилось и Студией и Интелом, и Gcc. Однако, при компиляции под Линуксом с максимальной оптимизацией начало падать. Вот я и хочу понять, я недопонимаю язык, или это ошибки компилятора.


Напиши отдельный метод, который закрывает файл и сбрасывает handle, и пусть деструктор его вызывает, и твоя функция, которой надо закрыть файл, тоже.
Re[5]: Жизнь после деструктора
От: Pzz Россия https://github.com/alexpevzner
Дата: 22.10.20 18:37
Оценка:
Здравствуйте, Lis182, Вы писали:

L>Я хочу понять, проблема в поем недопонимании языка, или таки в ошибках компилятора.


А чем тебе поможет это понимание?
Re[3]: Жизнь после деструктора
От: Свободу rg45! СССР  
Дата: 22.10.20 18:40
Оценка:
Lis182:

L>

L>    inline ~SFile()
L>    {
L>        if (hFile != INVALID_HANDLE_VALUE)
L>            CloseHandle(hFile), hFile = INVALID_HANDLE_VALUE;
L>    }

L>


L>Смысл в том, чтоб файл закрывался при выпрыгивания из функции в любом месте. Как не сложно видеть, файл закроется только если он не был закрыт. Но мне потребовалось закрывать этот файл посередине функции (потом этот файл переименовывался). Ну я и вставил вызов деструктора. Это компилилось и Студией и Интелом, и Gcc. Однако, при компиляции под Линуксом с максимальной оптимизацией начало падать. Вот я и хочу понять, я недопонимаю язык, или это ошибки компилятора.


Вызывать деструктор явно — это очень плохо.
Нужен метод Close(), который закрывает hFile если он не INVALID_HANDLE_VALUE и сбрасывает hFile в INVALID_HANDLE_VALUE.
А еще лучше применить SmartHandle (аналог SmaprPointer), тут на форуме проходил такой велосипед много лет назад.
Модератор-националист Kerk преследует оппонентов по политическим мотивам.
Re[6]: Жизнь после деструктора
От: Lis182 Россия  
Дата: 22.10.20 18:44
Оценка: -1 :)
L>>Я хочу понять, проблема в поем недопонимании языка, или таки в ошибках компилятора.

Pzz>А чем тебе поможет это понимание?


Ну во первых, оценю степень глючности разных компиляторов. Во вторых, если всетки это компилятор, похвастаюсь начальству, которое очень скептически реагировало на мои заявления, что я (и чел, который переписывал мой код) в общем-то не виноваты в проблеме.
Re[4]: Жизнь после деструктора
От: Lis182 Россия  
Дата: 22.10.20 18:55
Оценка: -1 :)
СR>Вызывать деструктор явно — это очень плохо.
СR>Нужен метод Close(), который закрывает hFile если он не INVALID_HANDLE_VALUE и сбрасывает hFile в INVALID_HANDLE_VALUE.
СR>А еще лучше применить SmartHandle (аналог SmaprPointer), тут на форуме проходил такой велосипед много лет назад.

Спасибо, конечно, я примерно так и сделал. Только я говорю не про красоту кода, а стандарты. Как я понимаю, такой мой вызов деструктора работать должен, и если он не работает, это проблема компилятора.
Re[4]: Жизнь после деструктора
От: Lis182 Россия  
Дата: 22.10.20 19:04
Оценка:
σ>Первое. Компилятор правомерно выкидывает бесполезное присваивание `hFile = INVALID_HANDLE_VALUE`.

На счет пустого присваивания — то это зря. Например, если так:


struct SMyStruct
{
    int val;
    ~SMyStruct()
    {
        val = 123;
    }
};

SMyStruct obj;
obj.~SMyStruct()
int a = 456 + obj.val;
// и т.д.


Если я правильно понимаю язык, то как не оптимизируй, функция должна быть вызвана. А в примере с файлом, она таки 2раза вызывается, и поле используется в условии.
Re[3]: Жизнь после деструктора
От: T4r4sB Россия  
Дата: 22.10.20 19:05
Оценка:
Здравствуйте, Lis182, Вы писали:

L>Смысл в том, чтоб файл закрывался при выпрыгивания из функции в любом месте. Как не сложно видеть, файл закроется только если он не был закрыт. Но мне потребовалось закрывать этот файл посередине функции (потом этот файл переименовывался). Ну я и вставил вызов деструктора. Это компилилось и Студией и Интелом, и Gcc. Однако, при компиляции под Линуксом с максимальной оптимизацией начало падать. Вот я и хочу понять, я недопонимаю язык, или это ошибки компилятора.


Вообще в таких случаях надо либо добавить метод close(), либо использовать Optional чтоб явно чистить.
Re[5]: Жизнь после деструктора
От: σ  
Дата: 22.10.20 19:11
Оценка:
σ>>Первое. Компилятор правомерно выкидывает бесполезное присваивание `hFile = INVALID_HANDLE_VALUE`.

L>На счет пустого присваивания — то это зря. Например, если так:


L>

L>struct SMyStruct
L>{
L>    int val;
L>    ~SMyStruct()
L>    {
L>        val = 123;
L>    }
L>};

L>SMyStruct obj;
L>obj.~SMyStruct()
L>int a = 456 + obj.val;
L>// и т.д.

L>


L>Если я правильно понимаю язык, то как не оптимизируй, функция должна быть вызвана. А в примере с файлом, она таки 2раза вызывается


Что есть UB.
Re[6]: Жизнь после деструктора
От: Lis182 Россия  
Дата: 22.10.20 19:14
Оценка: +1
σ>Что есть UB.

Терзают меня смутные сомнения, что UB...
Re[5]: Жизнь после деструктора
От: a7d3  
Дата: 22.10.20 19:37
Оценка:
Здравствуйте, Lis182, Вы писали:


A>>Тогда ещё раз, тоже самое, но другими словами.

A>>Компиляция выполняется в результате многократного числа проходов по коду. На каждом из проходов что-то добавляется, что-то упрощается или выкидывается. Каждый проход использует результаты предыдущих. А теперь добавляем в код руками то самое, что могло или должно было быть добавлено в результате одного из таких проходов. Как именно тогда будет действовать компилятор?

L>Спасибо за ответ Но деструктор это функция, и как любая функция должен вызываться если это указано в коде. У меня только сомнения в обязательности хранения объекта до выхода из области видимости. У меня этот код отлично работал с разными компиляторами много лет. А тут, под Линукс с полной оптимизацией стало падать. Причем вызовы то выполняются, только криво. Может быть this кривой, может таки стек очищается. Дизассемблировать, чтоб точно определить весьма сложно. Я хочу понять, проблема в поем недопонимании языка, или таки в ошибках компилятора.


Проблема в хреновом знании С++.
  1. Повторный вызов деструктора для экземпляра класса является UB и его использование однозначно говорит об уровне познаний аффтара кода.
  2. Есть два варианта вызова деструктора, если используется тот, который «Virtual call», то для его корректного выполнения требуется кое-что дополнительное, в сравнении с «non-virtual call».
Re[7]: Жизнь после деструктора
От: Mystic Artifact  
Дата: 22.10.20 19:48
Оценка:
Здравствуйте, Lis182, Вы писали:

Да ничего компилятор не выбрасывает и выбросить не может. Тот же clang 11 послушно будет генерировать два вызова деструктора.
Однако, учти, что если компилятор недайбог увидит что "CloseHandle" не имеет сайд-эффектов — то весь твой пример скомпилируется в пустой код, и это будет тоже правильно.
Re[8]: Жизнь после деструктора
От: σ  
Дата: 22.10.20 19:52
Оценка:
MA>Да ничего компилятор не выбрасывает и выбросить не может.

Анус ставишь?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.