Книжка по UB
От: LaptevVV Россия  
Дата: 12.08.25 04:02
Оценка: 99 (11) +2
Я тут давал адрес репы на гитхабе об UB
https://github.com/Nekrolm/ubbook

Автор собрался с силами, и написал книжку!
https://bhv.ru/product/ekskurs-v-neopredelennoe-povedenie-c/

А научным редактором и соавтором у него стал Андрей Карпов — основатель PVS

Рекомендую!
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re: Книжка по UB
От: Pzz Россия https://github.com/alexpevzner
Дата: 12.08.25 16:31
Оценка: +2
Здравствуйте, LaptevVV, Вы писали:

LVV>Рекомендую!


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

#include <string>

int main() {
    std::string s;
    s += 48;    // неявное приведение к char.
    s += 1000;  // а тут еще и с переполнением, очень неприятным
                // на платформе с signed char.
    s += 49.5;  // опять-таки неявное приведение к char
}


Матерную поэму. В этой поэме должно быть много красивых, выразительных и поэтичных русских матерных слов.
Re[2]: Книжка по UB
От: B0FEE664  
Дата: 12.08.25 17:17
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Полистал немного. Об этом надо писать поэму, а не книжку в прозе.

Да там много интересного.
Вот грабли, на которые я ещё не наступал:
const int max_v = 10;

void fun(int y) 
{
   const int max_v = [&]{
       // локальный max_v перекрывает глобальный max_v
       return std::min(max_v, y);
   }();
   ...
}
И каждый день — без права на ошибку...
Re[2]: Книжка по UB
От: LaptevVV Россия  
Дата: 12.08.25 17:40
Оценка: +1
Pzz>Полистал немного. Об этом надо писать поэму, а не книжку в прозе.
Pzz>
Pzz>#include <string>
Pzz>int main() {
Pzz>    std::string s;
Pzz>    s += 48;    // неявное приведение к char.
Pzz>    s += 1000;  // а тут еще и с переполнением, очень неприятным
Pzz>                // на платформе с signed char.
Pzz>    s += 49.5;  // опять-таки неявное приведение к char
Pzz>}
Pzz>

Pzz>Матерную поэму. В этой поэме должно быть много красивых, выразительных и поэтичных русских матерных слов.
Да.
Мне просто в голову такая хрень никогда не приходила! Ни разу в жизни такое не писал...
А оно вон оно чо, Михалыч!

Проверил на MinGW gcc 14.2 — компилируется!
Предупреждение выдает только на строку с 1000:
warning: overflow in conversion from 'int' to 'char' changes value from '1000' to '-24' [-Woverflow]

Даже на 49.5 — ничего не пишет!
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[2]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 12.08.25 18:23
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Матерную поэму. В этой поэме должно быть много красивых, выразительных и поэтичных русских матерных слов.


Эка вы в адрес своей любимой ламповой сишки раздухарились! Прям любо-дорого.

Эти же грабли прямиком оттуда унаследованы:
int main()
{
    char s[10];
    s[0] = 48;
    s[1] = 1000;
    s[2] = 49.5;
}

https://wandbox.org/permlink/Oaqs7fuRXVZNYkco
Re[3]: Книжка по UB
От: Pzz Россия https://github.com/alexpevzner
Дата: 12.08.25 18:30
Оценка: +1
Здравствуйте, LaptevVV, Вы писали:

Pzz>>Матерную поэму. В этой поэме должно быть много красивых, выразительных и поэтичных русских матерных слов.

LVV>Да.
LVV>Мне просто в голову такая хрень никогда не приходила! Ни разу в жизни такое не писал...
LVV>А оно вон оно чо, Михалыч!

Там самая мякотка — в главе про неработающий синтаксис.

struct Timer {
    int val;
    explicit Timer(int v = 0) : val(v) {}
};

struct Worker {
    int time_to_work;

    explicit Worker(Timer t) : time_to_work(t.val) {}

    friend std::ostream& operator << (std::ostream& os, const Worker& w) {
        return os << "Time to work=" << w.time_to_work;
    }
};

int main() {
    // ЭТО НЕ ВЫЗОВ КОНСТРУКТОРА!
    Worker w(Timer()); // предобъявление функции, которая возвращает Worker и принимает функцию, возвращающую Timer и не принимающую ничего!

    std::cout << w; // имя функции неявно преобразуется к указателю, который неявно преобразуется к bool
    // будет выведено 1 (true)
}
Re[3]: Книжка по UB
От: Pzz Россия https://github.com/alexpevzner
Дата: 12.08.25 18:44
Оценка: +2
Здравствуйте, so5team, Вы писали:

Pzz>>Матерную поэму. В этой поэме должно быть много красивых, выразительных и поэтичных русских матерных слов.


S>Эка вы в адрес своей любимой ламповой сишки раздухарились! Прям любо-дорого.


В сишечке это не имеет таких последствий. Си, всё-таки, простой, обозримый, компактный язык. К тому же с каждого, кто рискует программировать на чистом Си, мироздание берет подписку, что все отстреленные ноги за счет стрелка, претензии к безопасности пулемета не принимаются. Что несколько дисциплинирует, по крайней мере, выживших.
Re[4]: Книжка по UB
От: LaptevVV Россия  
Дата: 12.08.25 18:48
Оценка:
Pzz>Там самая мякотка — в главе про неработающий синтаксис.
Pzz>
...
Pzz>    // ЭТО НЕ ВЫЗОВ КОНСТРУКТОРА!
Pzz>    Worker w(Timer()); // предобъявление функции, которая возвращает Worker и принимает функцию, возвращающую Timer и не принимающую ничего!
Pzz>

Ну, я как привык смолоду писать попроще, через равенство — так и пишу.
Сейчас приходится переучиваться на {}
Но вообще — да.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[5]: Книжка по UB
От: Pzz Россия https://github.com/alexpevzner
Дата: 12.08.25 18:51
Оценка: +1
Здравствуйте, LaptevVV, Вы писали:

LVV>Ну, я как привык смолоду писать попроще, через равенство — так и пишу.

LVV>Сейчас приходится переучиваться на {}
LVV>Но вообще — да.

Это я выдернул первое, что под руку подвернулось. Там много весёлого.

Я понимаю, что половина этого в Си тоже есть. Но в Си нет всех этих прекрасных автоматизмов, которые способны превратить простую и понятную ошибку в хитрую или запутанную.
Re[3]: Книжка по UB
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 12.08.25 18:53
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Вот грабли, на которые я ещё не наступал:

BFE>
BFE>const int max_v = 10;

BFE>void fun(int y) 
BFE>{
BFE>   const int max_v = [&]{
BFE>       // локальный max_v перекрывает глобальный max_v
BFE>       return std::min(max_v, y);
BFE>   }();
BFE>   ...
BFE>}
BFE>


Это ещё с сишечки по-моему так. В любом случае, компилятор тебе скажет, как минимум предупреждение, что одна переменная скрывает другую. Но, конечно, если у тебя при компиляции "кучи ненужных варнингов" (c) (tm), но будут грабли.
Маньяк Робокряк колесит по городу
Re[4]: Книжка по UB
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 12.08.25 18:55
Оценка: +2
Здравствуйте, Pzz, Вы писали:


Pzz>Там самая мякотка — в главе про неработающий синтаксис.


Pzz>
Pzz>int main() {
Pzz>    // ЭТО НЕ ВЫЗОВ КОНСТРУКТОРА!
Pzz>    Worker w(Timer()); // предобъявление функции, которая возвращает Worker и принимает функцию, возвращающую Timer и не принимающую ничего!

Pzz>    std::cout << w; // имя функции неявно преобразуется к указателю, который неявно преобразуется к bool
Pzz>    // будет выведено 1 (true)
Pzz>}
Pzz>


Это все уже лет 30 как знают
Маньяк Робокряк колесит по городу
Re[4]: Книжка по UB
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 12.08.25 18:56
Оценка:
Здравствуйте, Pzz, Вы писали:

S>>Эка вы в адрес своей любимой ламповой сишки раздухарились! Прям любо-дорого.


Pzz>В сишечке это не имеет таких последствий. Си, всё-таки, простой, обозримый, компактный язык. К тому же с каждого, кто рискует программировать на чистом Си, мироздание берет подписку, что все отстреленные ноги за счет стрелка, претензии к безопасности пулемета не принимаются. Что несколько дисциплинирует, по крайней мере, выживших.


А какие последствия будут в приведённом случае? К строке прибавится символ? Чем это отличается от сишечного?
Маньяк Робокряк колесит по городу
Re[3]: Книжка по UB
От: Pzz Россия https://github.com/alexpevzner
Дата: 12.08.25 18:57
Оценка: +2
Здравствуйте, B0FEE664, Вы писали:

Pzz>>Полистал немного. Об этом надо писать поэму, а не книжку в прозе.

BFE>Да там много интересного.

Мне еще вот такое очень понравилось:

// пример взят из блога https://mohitmv.github.io/blog/Shocking-Undefined-Behaviour-In-Action/
int main() {
  char buf[50] = "y";
  for (int j = 0; j < 9; ++j) {
    std::cout << (j * 0x20000001) << std::endl;
    if (buf[0] == 'x') break;
  }
}


Тут компилятор решает вынести умножение из тела цикла. Заменяет ++j на j += 0x20000001, ну и верхнюю границу, девятку, тоже умножает на 0x20000001. А что, имеет право, целочисленное переполнение для знаковых типов — это UB. В итоге цикл на 9 оборотов становится бесконечным.
Re[5]: Книжка по UB
От: Pzz Россия https://github.com/alexpevzner
Дата: 12.08.25 19:00
Оценка:
Здравствуйте, Marty, Вы писали:

Pzz>>В сишечке это не имеет таких последствий. Си, всё-таки, простой, обозримый, компактный язык. К тому же с каждого, кто рискует программировать на чистом Си, мироздание берет подписку, что все отстреленные ноги за счет стрелка, претензии к безопасности пулемета не принимаются. Что несколько дисциплинирует, по крайней мере, выживших.


M>А какие последствия будут в приведённом случае? К строке прибавится символ? Чем это отличается от сишечного?


Здесь — ничем.

А вот когда к сишным (весма вольным и дурным) правилам преобразования типов добавляется сипплюсплюсная идея вложить в тип глубокий смысл, позволив через оверлоадинг приделать к разным типам разное поведение для одинаково выглядещих действий, жизнь становится уже весьма забавной.
Re[6]: Книжка по UB
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 12.08.25 19:12
Оценка:
Здравствуйте, Pzz, Вы писали:

M>>А какие последствия будут в приведённом случае? К строке прибавится символ? Чем это отличается от сишечного?


Pzz>Здесь — ничем.


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



Дурные правила преобразования типов работают только для интегральных типов и плавучки. Это полностью наследие сишечки. Для типов, в которые вложен глубокий смысл, всё весьма строго.
Маньяк Робокряк колесит по городу
Re[4]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 12.08.25 19:33
Оценка: 2 (1) +1
Здравствуйте, Pzz, Вы писали:

Pzz>>>Матерную поэму. В этой поэме должно быть много красивых, выразительных и поэтичных русских матерных слов.


S>>Эка вы в адрес своей любимой ламповой сишки раздухарились! Прям любо-дорого.


Pzz>В сишечке это не имеет таких последствий.


Вот объясните мне, а то я не понимаю это что: глупость, двойные стандарты или и то, и другое?
Ибо когда в Си из-за дебильных правил приведения типа хз какой char автоматически и неявно генерируется из double-литерала, то это OK.
А вот когда в С++ в точности тоже самое происходит из-за тех же самых (именно что тех же самых, унаследованных из Си) правил -- то это уже мать-мать-мать-перемать.

Ну вот как это в одной башке уживается?

Pzz>К тому же с каждого, кто рискует программировать на чистом Си, мироздание берет подписку, что все отстреленные ноги за счет стрелка, претензии к безопасности пулемета не принимаются. Что несколько дисциплинирует, по крайней мере, выживших.


Вы, видимо, забыли (а может никогда и не знали) старый афоризм: Си позволяет вам выстрелить себе в ногу, в C++ это сделать сложнее, но когда вы это таки сделаете, то вам отрывает нахрен сразу обе ноги.

Ну и чтобы еще набросить на вашу пустую седую голову: в современном С++ хотя бы можно написать s += char{49.5} и получить ошибку компиляции. А вот что здесь может предложить Си?
Re[5]: Книжка по UB
От: Pzz Россия https://github.com/alexpevzner
Дата: 12.08.25 19:36
Оценка: +1
Здравствуйте, so5team, Вы писали:

S>Ну и чтобы еще набросить на вашу пустую седую голову: в современном С++ хотя бы можно написать s += char{49.5} и получить ошибку компиляции. А вот что здесь может предложить Си?


Мы с вами уже договаривались, что либо вы не хамите, либо мы не общаемся.

Прощайте!
Re: Книжка по UB
От: __kot2  
Дата: 13.08.25 06:12
Оценка:
на озоне, кстати, тоже продается. заказал
Re: Книжка по UB
От: Privalov  
Дата: 13.08.25 07:55
Оценка: +1
Здравствуйте, LaptevVV, Вы писали:

LVV>Рекомендую!


Вспомнилось. Во времена Фортрана литературы "Грабли в Фортране" было полно. Мы даже что-то проверяли, когда учились. Порчу константы или беспорядочное использование COMMON, особенно безымянных. Зато в реальной работе граблей избегать удавалось.
А на C/C++ мне довелось написать совсем немного кода. Всякие там JNI и вставки к фортрановскому проекту.
Но судя по опыту работы с Фортраном, такая литература должна быть полезной.
Re[2]: Книжка по UB
От: LaptevVV Россия  
Дата: 13.08.25 09:19
Оценка:
P>Вспомнилось. Во времена Фортрана литературы "Грабли в Фортране" было полно. Мы даже что-то проверяли, когда учились. Порчу константы или беспорядочное использование COMMON, особенно безымянных. Зато в реальной работе граблей избегать удавалось.
Да, это я тоже знаю...
P>А на C/C++ мне довелось написать совсем немного кода. Всякие там JNI и вставки к фортрановскому проекту.
P>Но судя по опыту работы с Фортраном, такая литература должна быть полезной.
Она очень полезна.
Для начинающих — откровение увидеть, как сложение 2 положительных становится отрицательным.
А в этой книжке поглубже, для мидлов.
Неявные преобразования — зло абсолютное.
Теперь у меня для студентов есть аргументированный источник примеров.
Не совсем уж тривиальных.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[3]: Книжка по UB
От: Privalov  
Дата: 13.08.25 09:48
Оценка: +1
Здравствуйте, LaptevVV, Вы писали:

LVV>Она очень полезна.


В C/C++ хотя бы зарезервированные слова есть. В Фортране и этого не было. Да ещё он пробелы игнорировал.

LVV>Для начинающих — откровение увидеть, как сложение 2 положительных становится отрицательным.


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

LVV>Неявные преобразования — зло абсолютное.


Повбывав бы.

LVV>Теперь у меня для студентов есть аргументированный источник примеров.

LVV>Не совсем уж тривиальных.

Да не только для студентов. Я вот ни разу не плюсовик, но иногда, довольно редко, пригодится и сейчас его брать. Всякие мелкие плагины делать. Шанс нарваться на грабли весьма высок.
Re[4]: Книжка по UB
От: alpha21264 СССР  
Дата: 13.08.25 10:03
Оценка: :)))
Здравствуйте, Marty, Вы писали:

M> Но, конечно, если у тебя при компиляции "кучи ненужных варнингов" (c) (tm), но будут грабли.


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

Течёт вода Кубань-реки куда велят большевики.
Re[5]: Книжка по UB
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 13.08.25 10:43
Оценка:
Здравствуйте, alpha21264, Вы писали:

M>> Но, конечно, если у тебя при компиляции "кучи ненужных варнингов" (c) (tm), но будут грабли.


A>Все твои проблемы от того, что ты не понимаешь слова.

A>В данном случае ты не понимаешь, что варнинги бывают "нужные" и "ненужные".


Не бывает ненужных варнингов. Все варнинги нужные. Каждый варнинг надо разбирать, смотреть, почему он был выдан.

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

                target_compile_options(${TARGET} PRIVATE "/WX" "/external:anglebrackets" "/external:W1")

                # !!! Надо разобратся со всеми этими варнингами. Пока дизаблим

                # warning C4435: 'TYPE': Object layout under /vd2 will change due to virtual base 'TYPE_BASE'
                target_compile_options(${TARGET} PRIVATE "/wd4435")

                # warning C4464: exclude warning "relative include path contains '..'"
                target_compile_options(${TARGET} PRIVATE "/wd4464")

                # warning C4505: unreferenced function has been removed
                target_compile_options(${TARGET} PRIVATE "/wd4514")

                # warning C4626: 'TYPE': assignment operator was implicitly defined as deleted
                target_compile_options(${TARGET} PRIVATE "/wd4626")

                # https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4710?view=msvc-170
                # warning C4710: function not inlined
                target_compile_options(${TARGET} PRIVATE "/wd4710")

                # warning C4711: function selected for automatic inline expansion
                target_compile_options(${TARGET} PRIVATE "/wd4711")

                # warning C4738: storing 32-bit float result in memory, possible loss of performance
                target_compile_options(${TARGET} PRIVATE "/wd4738")

                # warning C4810: value of pragma pack(show)
                # target_compile_options(${TARGET} PRIVATE "/wd4810")
                
                # warning C4820: N bytes padding added after data member 'memberName'
                target_compile_options(${TARGET} PRIVATE "/wd4820")

                # warning C4866: compiler may not enforce left-to-right evaluation order for call to 'umba::SimpleFormatter::operator<<<std::basic_string<char,std::char_traits<char>,std::allocator<char> > >'
                target_compile_options(${TARGET} PRIVATE "/wd4866")

                # warning C5027: 'TYPE': move assignment operator was implicitly defined as deleted
                target_compile_options(${TARGET} PRIVATE "/wd5027")

                # warning C5045: Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified
                target_compile_options(${TARGET} PRIVATE "/wd5045")



Ещё у меня есть специальные инклюды (но они используются очень редко), которыми я оборачиваю проблемное место после разбирательства (лежат в моей "главной" базовой либе в подкаталоге warnings):
pop.h
push_disable_condition_is_const.h
push_disable_def_ctor_implicitly_deleted.h
push_disable_enumerator_not_handled.h
push_disable_fn_or_var_unsafe.h
push_disable_implicit_copy_ctor_deprecated.h
push_disable_non_portable_variadic.h
push_disable_non_trivial_destructor_not_virtual.h
push_disable_padding_added.h
push_disable_rel_inc_contains_dbldot.h
push_disable_result_not_used.h
push_disable_signed_unsigned_mismatch.h
push_disable_spectre_mitigation.h
push_disable_this_used_in_base_member_initializer_list.h
push_disable_trivial_destructor_not_virtual.h
push_disable_unsafe_conversion.h


Т.е. я делаю так:
  #include "mycore/warnings/push_disable_non_trivial_destructor_not_virtual.h"
  // Проблемный код
  #include "mycore/warnings/pop.h"



Всё. Один раз я это сделал в основном (и раз в несколько месяцев пополняю список warn-инклюдов)

Больше никаких проблем. И теперь любой варнинг, который я вижу, не является бесполезным, а указывает с большой вероятностью на мой косяк.
Маньяк Робокряк колесит по городу
Re: Книжка по UB
От: Лазар Бешкенадзе СССР  
Дата: 13.08.25 11:34
Оценка: -2
Здравствуйте, LaptevVV, Вы писали:

LVV>Рекомендую!


Валерий Викторович, я очень давно не программировал но в свое время неплохо разбирался в C++98. Я не думаю что в механизме виртуальных функций что-нибудь изменилось с тех пор. Поэтому сообщаю вам что вот в этом разделе написана абсолютная чушь:

https://github.com/Nekrolm/ubbook/blob/master/runtime/virtual_functions.md

Человек пишет о том в чем не разобрался и судя по всему даже не заглядывал в текст стандарта.

А вы мне советуете выбросить на ветер 1140 рублей. Нехорошо.

-
Re[2]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 13.08.25 11:41
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>Валерий Викторович, я очень давно не программировал но в свое время неплохо разбирался в C++98. Я не думаю что в механизме виртуальных функций что-нибудь изменилось с тех пор. Поэтому сообщаю вам что вот в этом разделе написана абсолютная чушь:


ЛБ>https://github.com/Nekrolm/ubbook/blob/master/runtime/virtual_functions.md


ЛБ>Человек пишет о том в чем не разобрался и судя по всему даже не заглядывал в текст стандарта.


И что конкретно там не так?
Re[3]: Книжка по UB
От: Лазар Бешкенадзе СССР  
Дата: 13.08.25 12:00
Оценка:
Здравствуйте, so5team, Вы писали:

S>И что конкретно там не так?


Все. Там написано:

В конструкторах и деструкторах в C++ виртуальная диспетчеризация методов не работает


Это чушь. Работает.

Единственная проблема в приведенном коде — вызов чисто виртуальной функции в ctor/dtor. Убери в этом коде =0 при описании виртуальных функций и их можно вызывать в ctor/dtor.

-
Re[4]: Книжка по UB
От: __kot2  
Дата: 13.08.25 12:04
Оценка:
ЛБ>Единственная проблема в приведенном коде — вызов чисто виртуальной функции в ctor/dtor. Убери в этом коде =0 при описании виртуальных функций и их можно вызывать в ctor/dtor.
указатель на виртуальную ф-ию прописывается при вызове конструктора. вызывая виртуальную ф-ию из конструктора или деструктора человек непонятно чего собирается добиться.
Re[4]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 13.08.25 12:31
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>Все. Там написано:


ЛБ>

В конструкторах и деструкторах в C++ виртуальная диспетчеризация методов не работает


ЛБ>Это чушь. Работает.


Правда что ли? А это тогда как объяснить:
#include <iostream>

class base
{
    virtual void f() { std::cout << "base::f" << std::endl; }
    
public:
    base() {
        std::cout << "base {" << std::endl;
        f();
        std::cout << "base }" << std::endl;
    }
    
    void call_f() {
        std::cout << "===" << std::endl;
        f();
    }
};

class derived : public base
{
    void f() { std::cout << "derived::f" << std::endl; }
    
public:
    derived() {
        std::cout << "derived {" << std::endl;
        f();
        std::cout << "derived }" << std::endl;
    }
};

int main()
{
    derived d;
    
    d.call_f();
}

вывод:
base {
base::f
base }
derived {
derived::f
derived }
===
derived::f


Цинк

ЗЫ. Для компиляции в режиме C++11 или более свежих стандартов для f нужно поставить override. Я просто в режиме C++98 проверял.
Отредактировано 13.08.2025 12:37 so5team . Предыдущая версия . Еще …
Отредактировано 13.08.2025 12:36 so5team . Предыдущая версия .
Отредактировано 13.08.2025 12:32 so5team . Предыдущая версия .
Re[5]: Книжка по UB
От: Лазар Бешкенадзе СССР  
Дата: 13.08.25 12:40
Оценка:
Здравствуйте, so5team, Вы писали:

S>Правда что ли? А это тогда как объяснить:


Читайте стандарт. В стандарте 98 года было так:

12.7 (3)
... When a virtual function is called directly or indirectly from a constructor (including from the mem-initializer for a data-member) or from a destructor, and the object to which a call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructor's own class or in one of its bases, ...


S>ЗЫ. Для компиляции в режиме C++11 или более свежих стандартов для f нужно поставить override. Я просто в режиме C++98 проверял.


Спасибо. Буду знать.

-
Re[6]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 13.08.25 12:44
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>Здравствуйте, so5team, Вы писали:


S>>Правда что ли? А это тогда как объяснить:


ЛБ>Читайте стандарт. В стандарте 98 года было так:


ЛБ>

12.7 (3)
ЛБ>... When a virtual function is called directly or indirectly from a constructor (including from the mem-initializer for a data-member) or from a destructor, and the object to which a call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructor's own class or in one of its bases, ...


Тогда какие претензии к главе статьи? Там именно об этом речь и идет. И мой пример именно это и демонстрирует.
Re[4]: Книжка по UB
От: rg45 СССР  
Дата: 13.08.25 12:50
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>

В конструкторах и деструкторах в C++ виртуальная диспетчеризация методов не работает


ЛБ>Это чушь. Работает.


ЛБ>Единственная проблема в приведенном коде — вызов чисто виртуальной функции в ctor/dtor. Убери в этом коде =0 при описании виртуальных функций и их можно вызывать в ctor/dtor.


Вызвать-то можно, весь вопрос только в том, что из этого получится. Некоторые ошибочно ожидают, что будет вызвана версия виртуальной функции из наследника, а вызвается либо собственная, либо вообще версия из базового класса, если собственной не предоставлено. Этим же объясняется, почему вообще возможeн pure virtual call.

https://coliru.stacked-crooked.com/a/4b8b034cd0190879

#include <iostream>
#include <memory>

class Base
{
public:

    virtual void Stop() { std::cout << "Base::Stop" << std::endl; }
    virtual ~Base() { 
        Stop(); // Вот здесь будет вызван Base::Stop, а не Derived::Stop, как некоторые ожидают.
    }
};

class Derived : public Base
{
public:
    virtual void Stop() { std::cout << "Derived::Stop" << std::endl; }
};

int main() {
    std::shared_ptr<Derived> derived_ptr = std::make_shared<Derived>();
    // При выходе: "Base::stop"
}
--
Справедливость выше закона. А человечность выше справедливости.
Re[7]: Книжка по UB
От: Лазар Бешкенадзе СССР  
Дата: 13.08.25 12:53
Оценка:
Здравствуйте, so5team, Вы писали:

S>Тогда какие претензии к главе статьи? Там именно об этом речь и идет. И мой пример именно это и демонстрирует.


Там про UB. Там написано не работает. А на самом деле работает и никакого UB от этого нет. UB там по другой причине — вызов pure virtual function в ctor/dtor. И как раз об этом автор ничего не говорит.

-
Re[8]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 13.08.25 13:01
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>Там написано не работает. А на самом деле работает


И как же оно работает, если при вызове виртуального метода, у которого есть новая версия в производном классе, вызывается все-таки версия из базового класса?

Ведь виртуальность в том и состоит, что мы дергаем метод через указатель/ссылку на базовый класс, а получаем вызов кода из производного класса. Но в конструкторах/деструкторах именно этого-то эффекта и нет.

Именно об этом автор книги и пишет русским по белому:

В конструкторах и деструкторах в C++ виртуальная диспетчеризация методов не работает

Re[9]: Книжка по UB
От: Лазар Бешкенадзе СССР  
Дата: 13.08.25 13:09
Оценка:
Здравствуйте, so5team, Вы писали:

S>И как же оно работает, если при вызове виртуального метода, у которого есть новая версия в производном классе, вызывается все-таки версия из базового класса?


Вот до определенного уровня — до класса now under construction и работает. Если у тебя там 10 базовых классов и ты сделал override только во втором то этот override и будет вызван.

-
Re[10]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 13.08.25 13:12
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>Вот до определенного уровня — до класса now under construction и работает.


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

И в конструкторах/деструкторах она не работает.
Re[5]: Книжка по UB
От: Лазар Бешкенадзе СССР  
Дата: 13.08.25 13:18
Оценка: -2
Здравствуйте, rg45, Вы писали:

R>Некоторые ошибочно ожидают


Почему меня должны волновать чьи-то ошибочные ожидания. Мне приводят в качестве примера код:

class Processor {
public:
    virtual void start() = 0;
    // stops execution, returns `false` if already stopped
    virtual bool stop() = 0;
    virtual ~Processor() {
        stop();
    }
};


И пишут что здесь проблема в том что "в конструкторах и деструкторах в C++ виртуальная диспетчеризация методов не работает". Я повторяю она работает и в стандарте написано как работает. Здесь всего одна проблема — виртуальный вызов чисто виртуальной функции. Вот это UB. И как раз об этом там ни слова.

-
Re[10]: Книжка по UB
От: rg45 СССР  
Дата: 13.08.25 13:23
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>Вот до определенного уровня — до класса now under construction и работает. Если у тебя там 10 базовых классов и ты сделал override только во втором то этот override и будет вызван.


Тут вот какая штука. В данном примере, вызов виртуальный фунции stop() в деструкторе ЗАВЕДОМО будет эквивалентен невиртуальной форме вызова: Processor::stop. Как при этом можно говорить о работоспособности виртуальной диспетчеризации? Устройство виртуальных механизмов стандарт языка ведь никак не регламентирует и в разных имплементациях языка он может быть разным.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 13.08.2025 13:25 rg45 . Предыдущая версия .
Re[2]: Книжка по UB
От: LaptevVV Россия  
Дата: 13.08.25 13:25
Оценка:
ЛБ>Валерий Викторович, я очень давно не программировал но в свое время неплохо разбирался в C++98. Я не думаю что в механизме виртуальных функций что-нибудь изменилось с тех пор. Поэтому сообщаю вам что вот в этом разделе написана абсолютная чушь:
ЛБ>https://github.com/Nekrolm/ubbook/blob/master/runtime/virtual_functions.md
ЛБ>Человек пишет о том в чем не разобрался и судя по всему даже не заглядывал в текст стандарта.
Ну, выше уже знатоки расписали...
ЛБ>А вы мне советуете выбросить на ветер 1140 рублей. Нехорошо.
Ну, ничего такого я не советовал.
Я рекомендовал книжку почитать.
Вот вы почитали, и пост родился. Другие заинтересуются...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Отредактировано 13.08.2025 14:04 LaptevVV . Предыдущая версия .
Re[6]: Книжка по UB
От: rg45 СССР  
Дата: 13.08.25 13:27
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>И пишут что здесь проблема в том что "в конструкторах и деструкторах в C++ виртуальная диспетчеризация методов не работает". Я повторяю она работает и в стандарте написано как работает. Здесь всего одна проблема — виртуальный вызов чисто виртуальной функции. Вот это UB. И как раз об этом там ни слова.


Какие у тебя есть основания утверждать, что виртуальная диспетчеризация работает, когда вызов stop() ЗАВЕДОМО эквивалентен невиртуальному вызову Processor::stop()?

"Можно вызвать" не то же самое, что "работает виртуальная диспетчеризация".
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 13.08.2025 13:28 rg45 . Предыдущая версия .
Re[7]: Книжка по UB
От: Лазар Бешкенадзе СССР  
Дата: 13.08.25 13:34
Оценка:
Здравствуйте, rg45, Вы писали:

R>вызов stop() ЗАВЕДОМО эквивалентен невиртуальному вызову Processor::stop()?


Вызов stop() в этом тексте — UB. Если бы там было Processor::stop() то все было бы законно. Так что тут нет никакой эквивалентности.

-
Re[7]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 13.08.25 13:41
Оценка:
Здравствуйте, rg45, Вы писали:

R>Какие у тебя есть основания утверждать, что виртуальная диспетчеризация работает


Я пришел к выводу, что по мнению Лазара работает, это когда вот так:

class base {
public:
  base() { f(); }

  virtual void f() {...}
};

class derived_first_level : public base {
public:
  derived_first_level() { f(); /* тут корректно будет вызван base::f() */ }
};

class derived_second_level : public derived_first_level {
public:
  derived_second_level() { f(); /* тут корректно будет вызван derived_second_level::f() */ }

  void f() override {...}
};

class derived_third_level : public derived_second_level {
public:
  derived_second_level() { f(); /* тут корректно будет вызван derived_second_level::f() */ }
};


ИМХО, именно этот сценарий Лазар и пытается нам описать.

То, что это к виртуальной диспетчеризации нормального человека не имеет отношения -- это уже другой вопрос. Но ведь работает же
Re[8]: Книжка по UB
От: rg45 СССР  
Дата: 13.08.25 13:47
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>Вызов stop() в этом тексте — UB.


Нет там такого утверждения.

Возможно, в тексте не хватает чёткости формулировок, что приводит к разночтениям. Как по мне, то смысл написанного как раз в том, что UB является результатом pure virtual call. И объясняется, откуда этот pure virtual call берётся.
--
Справедливость выше закона. А человечность выше справедливости.
Re[11]: Книжка по UB
От: watchmaker  
Дата: 13.08.25 13:59
Оценка:
Здравствуйте, so5team, Вы писали:

ЛБ>>Вот до определенного уровня — до класса now under construction и работает.



S>И в конструкторах/деструкторах она не работает.


Тогда, если быть последовательным, придётся принять утверждение: "в final-классах виртуальная диспетчеризация не работает". Как думаешь, хорошее и полезное это утверждение, чтобы его запоминать?

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

S>Виртуальная диспетчеризация либо работает, либо нет.


А постоянно мигающий жёлтым светофор на перекрёстке "работает" или нет?
В бытовом смысле — нет. А по ПДД — работает.


В статье используется отвратительно неоднозначная терминология.
Если бы было написано, что конкретно подразумевается под "не работает", то с этим ещё можно было бы как-то смириться.
Но сейчас там предлагается додумывать самому. А так как есть несколько хороших взаимноисключающих трактовок, то я вполне согласен с тем, что в статье написано некорректное утверждение — ведь оно в любом случае будет противоречить каким-то из них.
Отредактировано 13.08.2025 14:06 watchmaker . Предыдущая версия . Еще …
Отредактировано 13.08.2025 14:02 watchmaker . Предыдущая версия .
Re[12]: Книжка по UB
От: rg45 СССР  
Дата: 13.08.25 14:15
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Тогда, если быть последовательным, придётся принять утверждение: "в final-классах виртуальная диспетчеризация не работает". Как думаешь, хорошее и полезное это утверждение, чтобы его запоминать?


ИМХО, вполне приемлемая интерпретация, если ориентироваться на наблюдаемое поведение. У нас же нет даже способа доказать или опровергнуть использование виртуального вызова. Можно, конечно, заглянуть в сгенерированный машинный код какого-нибудь компилятора, но обобщить свои выводы мы всё равно не сможем.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 13.08.2025 15:16 rg45 . Предыдущая версия . Еще …
Отредактировано 13.08.2025 14:15 rg45 . Предыдущая версия .
Re[6]: Книжка по UB
От: Pzz Россия https://github.com/alexpevzner
Дата: 13.08.25 14:29
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>

12.7 (3)
ЛБ>... When a virtual function is called directly or indirectly from a constructor (including from the mem-initializer for a data-member) or from a destructor, and the object to which a call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructor's own class or in one of its bases, ...


Ну да. Позовётся виртуальная функция, соответствующая тому уровню создания объекта, к которому относится конструктор. А вот когда объект создастся до конца (т.е., не в конструкторе), то та же виртуальная функция будет соответствовать самому "верхнему" производному классу, который сподобился ее переопределить.

Стандарт и книжка говорят об одном.

Надеюсь хоть, деструктор ведет себя симметрично. Хотя кто его знает, чего ждать от этого плюсплюса.
Re[2]: Книжка по UB
От: Pzz Россия https://github.com/alexpevzner
Дата: 13.08.25 14:29
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>А вы мне советуете выбросить на ветер 1140 рублей. Нехорошо.


Можно же её с гитхаба бесплатно читать...
Re[3]: Книжка по UB
От: Pzz Россия https://github.com/alexpevzner
Дата: 13.08.25 14:33
Оценка: :))
Здравствуйте, LaptevVV, Вы писали:

ЛБ>>А вы мне советуете выбросить на ветер 1140 рублей. Нехорошо.

LVV>Ну, ничего такого я не советовал.
LVV>Я рекомендовал книжку почитать.
LVV>Вот вы почитали, и пост родился. Другие заинтересуются...

Причём забесплатно, небось, почитал. А ты ему, выходит, теперь 1140 рублей должен.
Re[9]: Книжка по UB
От: Лазар Бешкенадзе СССР  
Дата: 13.08.25 14:49
Оценка:
Здравствуйте, rg45, Вы писали:

ЛБ>>Вызов stop() в этом тексте — UB.


R>Нет там такого утверждения.


Это мое утверждение о приведенном коде — текст здесь надо понимать как текст программы.

R>Возможно, в тексте не хватает чёткости формулировок, что приводит к разночтениям. Как по мне, то смысл написанного как раз в том, что UB является результатом pure virtual call. И объясняется, откуда этот pure virtual call берётся.


Вот что написано:

При деструктурировании наоборот — часть объекта-наследника уже уничтожена, и если позволить динамический вызов, можно легко получить use-after-free.


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

-
Re[12]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 13.08.25 14:54
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Тогда, если быть последовательным, придётся принять утверждение: "в final-классах виртуальная диспетчеризация не работает". Как думаешь, хорошее и полезное это утверждение, чтобы его запоминать?


Тут требуется пояснительная бригада ибо аппеляцию к final-классам не понял от слова совсем.

Я выше дал определение для виртуальности: "виртуальность в том и состоит, что мы дергаем метод через указатель/ссылку на базовый класс, а получаем вызов кода из производного класса"

Берем пример:
#include <iostream>

class base
{
    
public:
    base() {
        std::cout << "base {" << std::endl;
        f();
        std::cout << "base }" << std::endl;
    }
    
    virtual void f() { std::cout << "base::f" << std::endl; }
};

void call(base & b) { b.f(); }

class derived : public base
{
    void f() override { std::cout << "derived::f" << std::endl; }
    
public:
    derived() = default;
};

class another_derived final : public base
{
    void f() override { std::cout << "another_derived::f" << std::endl; }
    
public:
    another_derived() = default;
};

int main()
{
    derived d;
    another_derived ad; 
    
    std::cout << "---" << std::endl;
    
    call(d);
    call(ad);
}

цынк

И видим внутри call то, о чем я пишу: по ссылке на базовый класс вызывается f(). При этом по факту вызвается f из наследника. Вне зависимости от final или не-final.

Тогда как в конструкторе base у нас this указывает на base, по this вызывается f(), но это не f() из наследников.

Т.е. в данном случае (т.е. внутри конструктора) виртуальности у вызова нет. ЧТД.
Отредактировано 13.08.2025 14:58 so5team . Предыдущая версия .
Re[10]: Книжка по UB
От: Pzz Россия https://github.com/alexpevzner
Дата: 13.08.25 14:55
Оценка: +1
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>

При деструктурировании наоборот — часть объекта-наследника уже уничтожена, и если позволить динамический вызов, можно легко получить use-after-free.


ЛБ>Человек явно не понимает механизма и считает что при динамическом вызове может быть вызвано что-то из производного класса который уже разобран.


Человек явно пишет, что ничего из полуразобранного производного класса вызвано быть не может и объясняет почему.

Я удивлён, что ты понимаешь этот текст строго наоборот написанному.
Re[8]: Книжка по UB
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 13.08.25 21:49
Оценка:
Здравствуйте, so5team, Вы писали:


S>ИМХО, именно этот сценарий Лазар и пытается нам описать.


Да.


S>То, что это к виртуальной диспетчеризации нормального человека не имеет отношения -- это уже другой вопрос. Но ведь работает же


Это тоже виртуальная диспетчеризация
Маньяк Робокряк колесит по городу
Re[9]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 14.08.25 03:43
Оценка:
Здравствуйте, Marty, Вы писали:

S>>То, что это к виртуальной диспетчеризации нормального человека не имеет отношения -- это уже другой вопрос. Но ведь работает же


M>Это тоже виртуальная диспетчеризация


Если это "виртуальная диспетчеризация", то что же тогда в Java или в Ruby, или в Python? Супервиртуальная или как?
Re[10]: Книжка по UB
От: Лазар Бешкенадзе СССР  
Дата: 14.08.25 04:35
Оценка:
Здравствуйте, so5team, Вы писали:

S>>>То, что это к виртуальной диспетчеризации нормального человека не имеет отношения -- это уже другой вопрос. Но ведь работает же


M>>Это тоже виртуальная диспетчеризация


S>Если это "виртуальная диспетчеризация", то что же тогда ...?


Вот ты говоришь здесь есть виртуальная диспетчеризация а здесь ее нет а я не видел таких различий в стандарте, ты "выше даешь определение виртуальности," а я не видел такого определения в стандарте.

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

И под виртуальной диспетчеризацией я понимаю механизм виртуального вызова виртуальных функций. Именно виртуального вызова потому что виртуальные фунции можно вызывать и статически (с явной квалификацией). Вот этот механизм в стандарте описан в том числе для вызова в ctor/dtor в параграфе часть которого я процитировал выше.

-
Re[11]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 14.08.25 04:48
Оценка: +2
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>Вот ты говоришь здесь есть виртуальная диспетчеризация а здесь ее нет а я не видел таких различий в стандарте, ты "выше даешь определение виртуальности," а я не видел такого определения в стандарте.


А книжка не текст стандарта обсуждает.

ЛБ>В стандарте делается различие между виртуальным вызовом и статическим, а твоих определений там нет.


И я не текст стандарта обсуждаю.

ЛБ>И под виртуальной диспетчеризацией я понимаю


Мне фиолетово что вы под виртуальной диспетчеризацией понимаете, т.к. речь идет не о вас, а о том, что написано в обсуждаемой книге. А там написано о сюрпризах, которые могут поджидать людей, по незнанию предполагающих, что в C++ вызов виртуального метода в конструкторе/деструкторе будет вести себя так же, как вызов оного метода в любом другом месте, и что в C++ будет совсем другое поведение, нежели во многих других языках с поддержкой ООП -- Java, C#, Ruby, Python). О чем автор буквально и пишет:

В конструкторах и деструкторах в C++ виртуальная диспетчеризация методов не работает (В других языках — например, в C# или Java — наоборот, что доставляет свои проблемы).


PS. Мне, блин, сложно понять почему кто-то доколупался к этому разделу книги. Я сам в свое время наступил на те же грабли. И с другими языками поработал, в которых поведение виртуальных методов в конструкторе отличное от C++ного. Поэтому то, что написано в этом разделе воспринимается просто, понятно и достоверно. А желающие доколупаться до букв идут лесом.
Re[9]: Книжка по UB
От: rg45 СССР  
Дата: 14.08.25 07:25
Оценка:
Здравствуйте, Marty, Вы писали:

S>>То, что это к виртуальной диспетчеризации нормального человека не имеет отношения -- это уже другой вопрос. Но ведь работает же


M>Это тоже виртуальная диспетчеризация


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

Так а стоп. А почему тогда мы уверены, что она работает? В чём это проявляется и как это можно проверить?
--
Справедливость выше закона. А человечность выше справедливости.
Re[10]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 14.08.25 07:45
Оценка: +2
Здравствуйте, rg45, Вы писали:

S>>>То, что это к виртуальной диспетчеризации нормального человека не имеет отношения -- это уже другой вопрос. Но ведь работает же


M>>Это тоже виртуальная диспетчеризация


R>Ну, хорошо. Виртуальная диспетчеризация работает, но её результат неотличим от прямого невиртуального вызова. Нет возражений против такой формулировки?


R>Так а стоп. А почему тогда мы уверены, что она работает? В чём это проявляется и как это можно проверить?


Пришел к выводу, что причина спора в этом обсуждении в том, что часть участников (Лазар, Marty, watchmaker) придерживаются той точки зрения, что механизм виртуальной диспетчеризации работает потому, что он работает именно так, как описано в стандарте. Т.е. если в программе происходит именно то, что и следует по стандарту языка C++, то значит все работает и работает так, как надо.

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

Т.е. есть как бы "виртуальный вызов с точки зрения общепринятых ожиданий" и есть "виртуальный вызов с точки зрения языка C++".

Поэтому одна группа (в которой я) говорит о том, что в конструкторе виртуальности нет.
А другая группа, говорит о том, что она есть (опираясь на стандарт C++).

Суть же в том, что проблемная глава ориентирована не на знатоков стандарта.
Re[11]: Книжка по UB
От: rg45 СССР  
Дата: 14.08.25 08:16
Оценка:
Здравствуйте, so5team, Вы писали:

S>Пришел к выводу, что причина спора в этом обсуждении в том, что часть участников (Лазар, Marty, watchmaker) придерживаются той точки зрения, что механизм виртуальной диспетчеризации работает потому, что он работает именно так, как описано в стандарте. Т.е. если в программе происходит именно то, что и следует по стандарту языка C++, то значит все работает и работает так, как надо.


Так стандарт же описывает каким должно быть поведение программы, а не детали реализации этого поведения. И единиственный вывод, который можно сделать из стандарта как раз тот самый — что вызов виртуальных функции из конструкторов/деструкторов даёт тот же эффект, что и прямой невиртуальный вызов.
--
Справедливость выше закона. А человечность выше справедливости.
Re[12]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 14.08.25 08:24
Оценка:
Здравствуйте, rg45, Вы писали:

S>>Пришел к выводу, что причина спора в этом обсуждении в том, что часть участников (Лазар, Marty, watchmaker) придерживаются той точки зрения, что механизм виртуальной диспетчеризации работает потому, что он работает именно так, как описано в стандарте. Т.е. если в программе происходит именно то, что и следует по стандарту языка C++, то значит все работает и работает так, как надо.


R>Так стандарт же описывает каким должно быть поведение программы, а не детали реализации этого поведения. И единиственный вывод, который можно сделать из стандарта как раз тот самый — что вызов виртуальных функции из конструкторов/деструкторов даёт тот же эффект, что и прямой невиртуальный вызов.


Ну не совсем:
class c1 {
  virtual void f() {...};
  ...
};

class c2 : public c1 {
  void f() override {...};
  ...
};

class c3 : public c2 {
  ... // нет своей версии f().
};

class c4 : public c3 {
public:
  c4() { f(); }
};


В конструкторе c4 мы не делаем прямой вызов c2::f, но именно эта версия и будет вызвана.
Re[13]: Книжка по UB
От: rg45 СССР  
Дата: 14.08.25 08:35
Оценка:
Здравствуйте, so5team, Вы писали:

S>Ну не совсем:

S>
S>class c1 {
S>  virtual void f() {...};
S>  ...
S>};

S>class c2 : public c1 {
S>  void f() override {...};
S>  ...
S>};

S>class c3 : public c2 {
S>  ... // нет своей версии f().
S>};

S>class c4 : public c3 {
S>public:
S>  c4() { f(); }
S>};
S>


S>В конструкторе c4 мы не делаем прямой вызов c2::f, но именно эта версия и будет вызвана.


Так мы в этом случае и просто f() не сможем вызвать. И оба вызова: f() и c4::f() создадут одинаковые ошибки компиляции. Тут не стоит забывать, что в данном случае c2::f — она же с3::f, она же c4::f. Т.е. даже в таком случае выходит, что эти вызовы неотличимы:

http://coliru.stacked-crooked.com/a/102a88c2bfba9e66

class c1 {
  virtual void f() {};
};

class c2 : public c1 {
  void f() override {};
};

class c3 : public c2 {
  // нет своей версии f().
};

class c4 : public c3 {
public:
  c4() {
      f();      // error: 'virtual void c2::f()' is private within this context
      c4::f();  // error: 'virtual void c2::f()' is private within this context
    }
};

int main() {
}


Если добавить спецификацю доступа public в с2 ошибка уйдёт, но, опять же, от обоих вызовов — виртуального и невиртуального получим одинаковый эффект.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 14.08.2025 8:39 rg45 . Предыдущая версия . Еще …
Отредактировано 14.08.2025 8:38 rg45 . Предыдущая версия .
Re[10]: Книжка по UB
От: Лазар Бешкенадзе СССР  
Дата: 14.08.25 08:42
Оценка:
Здравствуйте, rg45, Вы писали:

R>Так а стоп. А почему тогда мы уверены, что она работает?


Потому что работает так как описано в стандарте.

R>В чём это проявляется и как это можно проверить?


Я не обязан проверять правильность работы иначе как сравнением с тем что написано в стандарте.

Но я подозреваю что ты интересуешься тем как таки определить разницу между виртуальным и статическим вызовом.

1. Если это к тому же чисто виртуальная функция то с виртуальным вызовом налетаем на pure virtual function call
2. Если это обычная виртуальная функция то возможны два варианта:
a. компилятор сделал оптимизацию и заменил виртуальный вызов на статический — здесь разницы нет
b. компилятор не делал такой оптимизации — виртуальный вызов медленнее статического и можно измерить разницу в скорости

-
Re[14]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 14.08.25 08:46
Оценка:
Здравствуйте, rg45, Вы писали:

R>Если добавить спецификацю доступа public в с2 ошибка уйдёт, но, опять же, от обоих вызовов — виртуального и невиртуального получим одинаковый эффект.


Тут моя ошибка, нужно было f() описывать в public-части. Давайте считать, что f() доступен для вызова.

Поинт в том, что с точки зрения C++ в конструкторе с4 мы автоматически получаем вызов самой свежей версии f на данный момент.
Это происходит за счет того, что С++ корректно модифицирует таблицу виртуальных методов по мере конструирования класса.

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

Повторюсь: суть в том, что ожидания неопытного пользователя о поведении виртуальной диспетчеризации в конструкторах/деструкторах, принципиально не совпадают с суровой реальностью стандарта С++
Re[11]: Книжка по UB
От: rg45 СССР  
Дата: 14.08.25 08:50
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

R>>Так а стоп. А почему тогда мы уверены, что она работает?


ЛБ>Потому что работает так как описано в стандарте.


Ну ты же сам приводил цитату из стандарта:

https://rsdn.org/forum/cpp/8977254.1
Автор: Лазар Бешкенадзе
Дата: 13.08 15:40


12.7 (3)
... When a virtual function is called directly or indirectly from a constructor (including from the mem-initializer for a data-member) or from a destructor, and the object to which a call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructor's own class or in one of its bases, ...


Здесь написано 1) Виртуальную функцию можно вызвать из конструкторов и деструкторов (с чем никто и не спорил) и 2) Результат такого вызова будт точно таким же, как если бы мы позвали эту фунцию невиртуальным способом. Никаких утверждений о виртуальной диспетчеризации здесь нет.
--
Справедливость выше закона. А человечность выше справедливости.
Re[11]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 14.08.25 08:52
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>Я не обязан проверять правильность работы иначе как сравнением с тем что написано в стандарте.


Когда кто-то так упорно ссылается на стандарт, то невольно вспоминается прекрасное: https://godbolt.org/g/o4HxtU
Не, ну а чо? Все по стандарту.
Re[15]: Книжка по UB
От: rg45 СССР  
Дата: 14.08.25 08:55
Оценка:
Здравствуйте, so5team, Вы писали:

S>Тут моя ошибка, нужно было f() описывать в public-части. Давайте считать, что f() доступен для вызова.


Хорошо, исправляем:

http://coliru.stacked-crooked.com/a/bcaa3ac0cf3c2746

class c1 {
public:
  virtual void f() { std::cout << "c1::f" << std::endl; }
};

class c2 : public c1 {
public:
  void f() override { std::cout << "c2::f" << std::endl;};
};

class c3 : public c2 {
  // нет своей версии f().
};

class c4 : public c3 {
public:
  c4() {
      f();      // c2::f
      c2::f();  // c2::f
      c3::f();  // c2::f
      c4::f();  // c2::f
    }
};

int main() {
    c4{};
}


S>Поинт в том, что с точки зрения C++ в конструкторе с4 мы автоматически получаем вызов самой свежей версии f на данный момент.

S>Это происходит за счет того, что С++ корректно модифицирует таблицу виртуальных методов по мере конструирования класса.

S>А значит, с формальной точки зрения, виртуальная диспетчеризация работает.


Ну, это никак не проверяется через поведение программы. Результаты же идентичны.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 14.08.2025 8:57 rg45 . Предыдущая версия .
Re[12]: Книжка по UB
От: Лазар Бешкенадзе СССР  
Дата: 14.08.25 09:02
Оценка:
Здравствуйте, rg45, Вы писали:

R>

R>12.7 (3)
R>... When a virtual function is called directly or indirectly from a constructor (including from the mem-initializer for a data-member) or from a destructor, and the object to which a call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructor's own class or in one of its bases, ...


Ты уже валяешь дурака.

R>Здесь написано


R>1) Виртуальную функцию можно вызвать из конструкторов и деструкторов (с чем никто и не спорил)


Я неплохо владею английским и расскажу тебе по секрету — здесь это не написано.

R>2) Результат такого вызова будт точно таким же, как если бы мы позвали эту фунцию невиртуальным способом.


Здесь это не написано.

R>Никаких утверждений о виртуальной диспетчеризации здесь нет.


Я и не писал что здесь есть такие утверждения.

Ты спросил как проверить — я тебе вежливо ответил. Ты юлишь. Я и с тобой прощаюсь.

-
Re[16]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 14.08.25 09:04
Оценка:
Здравствуйте, rg45, Вы писали:

S>>А значит, с формальной точки зрения, виртуальная диспетчеризация работает.


R>Ну, это никак не проверяется через поведение программы. Результаты же идентичны.


Почему не проверяется? Согласно стандарту при вызове f() в конструкторе c4 мы получаем вызов наиболее свежей к этому времени версии f -- c2::f.
Значит все работает.

Аналогичная точка зрения у моих "оппонентов" и на ситуацию:
struct c1 {
  ~c1() { f(); }
  virtual void f() { std::cout << "c1::f" << std::endl; }
};

struct c2 : public c1 {};

struct c3 : public c2 {
  void f() override { std::cout << "c3::f" << std::endl; }
};

int main() {
  c3 c;
}

В деструкторе c1 будет вызвана наиболее свежая к этому времени версия f -- c1::f, а не c3::f.
В точности согласно стандарта, а значит все работает как и предписано. А значит все работает.

Т.е. работа "по стандарту" вполне себе проверяется через работу программы.

Вынужден повториться, но проблема здесь не в стандарте или возможности проверить работу согласно стандарта. А в том, что у части пользователей C++ другие ожидания от вызова виртуального метода в конструкторе/деструкторе.
Re[13]: Книжка по UB
От: rg45 СССР  
Дата: 14.08.25 09:08
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>Ты уже валяешь дурака.


У-ру-ру. Ты не газуй только так сильно. А то я тебе сейчас расскжу, кто здесь кого валяет.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 14.08.2025 9:10 rg45 . Предыдущая версия .
Re[17]: Книжка по UB
От: rg45 СССР  
Дата: 14.08.25 09:13
Оценка:
Здравствуйте, so5team, Вы писали:


S>Почему не проверяется? Согласно стандарту при вызове f() в конструкторе c4 мы получаем вызов наиболее свежей к этому времени версии f -- c2::f.

S>Значит все работает.

Ну так, если мы сделаем невиртуальный вызов: с4::f() мы получим тот же самый вызов c2::f.
--
Справедливость выше закона. А человечность выше справедливости.
Re[18]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 14.08.25 09:20
Оценка:
Здравствуйте, rg45, Вы писали:

S>>Почему не проверяется? Согласно стандарту при вызове f() в конструкторе c4 мы получаем вызов наиболее свежей к этому времени версии f -- c2::f.

S>>Значит все работает.

R>Ну так, если мы сделаем невиртуальный вызов: с4::f() мы получим тот же самый вызов c2::f.


Так ведь это здесь не при чем, вот в чем фокус.

Есть контекст -- конструктор c4.
В нем вызывается виртуальный метод f.
Стандарт описывает какая именно версия f будет вызвана в этом контексте.
Мы этот вызов и получаем. ЧТД.

А то, что в этом контексте вызов this->f() не будет отличаться от вызова c2::f(), ну так это стечение обстоятельств.
Re[19]: Книжка по UB
От: rg45 СССР  
Дата: 14.08.25 09:32
Оценка:
Здравствуйте, so5team, Вы писали:

S>Есть контекст -- конструктор c4.

S>В нем вызывается виртуальный метод f.
S>Стандарт описывает какая именно версия f будет вызвана в этом контексте.
S>Мы этот вызов и получаем. ЧТД.

S>А то, что в этом контексте вызов this->f() не будет отличаться от вызова c2::f(), ну так это стечение обстоятельств.


Давай пока забудем про вызов c2::f(). Мы рассматриваем конструктор класса f4 и для этого класса у нас есть два способа вызвать функцию f: 1) виртуальный — f() или this->f() и 2) невиртуальный — f4::f(). И согласно требованиям стандарта оба эти вызова должны дать один и тот же результат. Таким образом, на уровне поведения программы эти два способа неотличимы.

На всякий случай: я не утверждаю, что виртуальный вызов не выполняется. С точки зрения поведения программы это так же недоказуемо, как и неопровергаемо.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 14.08.2025 9:36 rg45 . Предыдущая версия .
Re[20]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 14.08.25 09:37
Оценка:
Здравствуйте, rg45, Вы писали:

R>Давай пока забудем про вызов c2::f(). Мы рассматриваем конструктор класса c4 и для этого класса у нас есть два способа вызвать функцию f: 1) виртуальный — f() или this->f() и 2) невиртуальный — c4::f(). И согласно требованиям стандарта оба эти вызова должны дать один и тот же результат. Таким образом, на уровне поведения программы эти два способа неотличимы.


Неотличимы. И что из этого должно следовать?
Re[21]: Книжка по UB
От: rg45 СССР  
Дата: 14.08.25 09:43
Оценка:
Здравствуйте, so5team, Вы писали:

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


R>>Давай пока забудем про вызов c2::f(). Мы рассматриваем конструктор класса c4 и для этого класса у нас есть два способа вызвать функцию f: 1) виртуальный — f() или this->f() и 2) невиртуальный — c4::f(). И согласно требованиям стандарта оба эти вызова должны дать один и тот же результат. Таким образом, на уровне поведения программы эти два способа неотличимы.


S>Неотличимы. И что из этого должно следовать?


Причем, неотличимы с точки зрения требований стандарта. Отсюда следует, что у нас нет никаких оснований, чтобы утверждать или отрицать "виртуальную диспетчеризацию" в конструкторах и деструкторах. О которой, кстати сказать, в стандарте вообще нет никаких упоминаний. Это термин, расчитанный на интуитивное восприятие. Тут было бы неплохо определиться для начала, о чём мы вообще спорим.
--
Справедливость выше закона. А человечность выше справедливости.
Re[22]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 14.08.25 09:49
Оценка:
Здравствуйте, rg45, Вы писали:

R>Причем, неотличимы с точки зрения требований стандарта. Отсюда следует, что у нас нет никаких оснований, чтобы утверждать или отрицать "виртуальную диспетчеризацию" в конструкторах и деструкторах. О которой, кстати сказать, в стандарте вообще нет никаких упоминаний. Это термин, расчитанный на интуитивное восприятие. Тут было бы неплохо определиться для начала, о чём мы вообще спорим.


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

Именно это определение и нарушается, когда мы рассматриваем ситуацию с вызовом виртуальных методов в конструкторах/деструкторах. В С++.

В других "более лучших"(tm) языках оно-то и не нарушается.
Re[23]: Книжка по UB
От: rg45 СССР  
Дата: 14.08.25 09:57
Оценка:
Здравствуйте, so5team, Вы писали:

S>Так я же не зря озвучил то определение, которым пользуюсь в рассуждениях: "виртуальность в том и состоит, что мы дергаем метод через указатель/ссылку на базовый класс, а получаем вызов кода из производного класса". Уверен, что автор книги подразумевал тоже самое.


Так и как можно проверить, каким образом дёрнули метод? Разве что, заглянуть в сгенерированный код. Ну так, во-первых, это знание неправомерно будет обощать на все компиляторы/платформы/конфигурации. А во-вторых, оптимизатор имеет полное право заменить виртуальный вызов на невиртуальный в конструкторе/деструкторе. Он ведь знает, какой нужно дёрнуть метод прямо во время компиляции!

S>Именно это определение и нарушается, когда мы рассматриваем ситуацию с вызовом виртуальных методов в конструкторах/деструкторах. В С++.


Если не трудно, можно ещё раз указать пункт, который нарушается?
--
Справедливость выше закона. А человечность выше справедливости.
Re[10]: Книжка по UB
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 14.08.25 09:59
Оценка:
Здравствуйте, rg45, Вы писали:

S>>>То, что это к виртуальной диспетчеризации нормального человека не имеет отношения -- это уже другой вопрос. Но ведь работает же


M>>Это тоже виртуальная диспетчеризация


R>Ну, хорошо. Виртуальная диспетчеризация работает, но её результат неотличим от прямого невиртуального вызова. Нет возражений против такой формулировки?


R>Так а стоп. А почему тогда мы уверены, что она работает? В чём это проявляется и как это можно проверить?


Подожди. Если в текущем наследнике виртуальный метод не переопределён, то что ты собрался вызывать невиртуально?
Маньяк Робокряк колесит по городу
Re[22]: Книжка по UB
От: Pzz Россия https://github.com/alexpevzner
Дата: 14.08.25 10:05
Оценка:
Здравствуйте, rg45, Вы писали:

R>Причем, неотличимы с точки зрения требований стандарта. Отсюда следует, что у нас нет никаких оснований, чтобы утверждать или отрицать "виртуальную диспетчеризацию" в конструкторах и деструкторах. О которой, кстати сказать, в стандарте вообще нет никаких упоминаний. Это термин, расчитанный на интуитивное восприятие. Тут было бы неплохо определиться для начала, о чём мы вообще спорим.


По-моему, вы спорите о терминах

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

Такое вот динамические связывание.

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

И поскольку это связывание динамическое, то вполне логично, что в процессе конструирования объекта с виртуальными методами связывание этих методов может меняться.

А дальше идёт простая оптимизация: пока отрабатывается конструктор базового класса, компилятор знает, что производный класс еще никто не создал, и может девиртуализировать виртуальный метод, позвав его напрямую. А может, собственно, этого и не делать, а позвать через указатель.

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

Что конечно порождает, на взгляд новичка, некоторую путаницу. Но видимо, причина этой путаницы состоит не в неудачной реализации в плюсах, а в том, что сама по себе концепция виртуальных методов достаточно сложна и не допускает простую реализацию (хотя если реализовать ее по-сишному, в виде калбеков в структуре, которые "производная" структура может переключить на себя, всё это и не кажется какой-то непонятной магией).
Re[24]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 14.08.25 10:07
Оценка:
Здравствуйте, rg45, Вы писали:

R>Так и как можно проверить, каким образом дёрнули метод?


А нам не важно как именно дернули -- через обращение к vtable или путем прямого вызова.
Нам важно какой метод в итоге был вызван. Т.е. конкретный механизм вызова всего лишь скучная деталь реализации.

S>>Именно это определение и нарушается, когда мы рассматриваем ситуацию с вызовом виртуальных методов в конструкторах/деструкторах. В С++.


R>Если не трудно, можно ещё раз указать пункт, который нарушается?


struct s1 {
  virtual void f() { std::cout << "s1::f" << std::endl; }

  s1() {
    this->f(); // (1)
  }

  void call_f() {
    this->f(); // (3)
  }
};

struct s2 : public s1 {
  void f() override { std::cout << "s2::f" << std::endl; }
};

int main() {
  s2 s; // (2)

  s.call_f(); // (4)
}


В точке (2) у нас конструируется объект типа s2, у которого должен быть собственный переопределенный f.

В точке (1) у нас на руках указатель на базовый тип s1 (это this через который идет вызов f).
Но в точке (1) вызов виртуального метода через указатель на базовый тип мы получаем не вызов s2::f, а вызов s1::f.

В точке (4) у нас все тот же объект типа s2.
Через вызов call_f мы приходим в точку (3), в которой у нас на руках опять указатель на базовый тип s1.
Однако, в точке (3) вызов виртуального метода через указатель на базовый тип мы получаем вызов s2::f, а не s1::f.

Тогда как в Java, Ruby или Python (подозреваю, что и в C#, и в D) мы в точках (1) и (3) получим одинаковое поведение.
Re[11]: Книжка по UB
От: rg45 СССР  
Дата: 14.08.25 10:08
Оценка: +1
Здравствуйте, Marty, Вы писали:

M>Подожди. Если в текущем наследнике виртуальный метод не переопределён, то что ты собрался вызывать невиртуально?


Вот, смотри, как раз этот случай. Здесь c2::f — она же с3::f, она же с4::f. Т.е. функция, определённая в базовом классе c2::f, может быть вызвана в наследнике невиртуальным способом с использование имени класса наследника — с4::f. Так работает наследование.

http://coliru.stacked-crooked.com/a/bcaa3ac0cf3c2746

#include <iostream>

class c1 {
public:
  virtual void f() { std::cout << "c1::f" << std::endl; }
};

class c2 : public c1 {
public:
  void f() override { std::cout << "c2::f" << std::endl;};
};

class c3 : public c2 {
  // нет своей версии f().
};

class c4 : public c3 {
public:
  c4() {
      f();      // c2::f
      c2::f();  // c2::f
      c3::f();  // c2::f
      c4::f();  // c2::f
    }
};

int main() {
    c4{};
}


Ещё одним важным моментом является тот факт, что при вызове виртуальной функции в конструкторе или деструкторе, компилятор знает, какой нужно дёрнуть метод прямо во время компиляции! И на кой чёрт, спрашивается, вызывать этот метод через дополнительную косвенность, когда это можно сделать напрямую?
--
Справедливость выше закона. А человечность выше справедливости.
Re[25]: Книжка по UB
От: rg45 СССР  
Дата: 14.08.25 10:17
Оценка:
Здравствуйте, so5team, Вы писали:

S>
S>struct s1 {
S>  virtual void f() { std::cout << "s1::f" << std::endl; }

S>  s1() {
    this->>f(); // (1)
S>  }

S>  void call_f() {
    this->>f(); // (3)
S>  }
S>};

S>struct s2 : public s1 {
S>  void f() override { std::cout << "s2::f" << std::endl; }
S>};

S>int main() {
S>  s2 s; // (2)

S>  s.call_f(); // (4)
S>}
S>


S>В точке (2) у нас конструируется объект типа s2, у которого должен быть собственный переопределенный f.


S>В точке (1) у нас на руках указатель на базовый тип s1 (это this через который идет вызов f).

S>Но в точке (1) вызов виртуального метода через указатель на базовый тип мы получаем не вызов s2::f, а вызов s1::f.

S>В точке (4) у нас все тот же объект типа s2.

S>Через вызов call_f мы приходим в точку (3), в которой у нас на руках опять указатель на базовый тип s1.
S>Однако, в точке (3) вызов виртуального метода через указатель на базовый тип мы получаем вызов s2::f, а не s1::f.

В точке (3) компилятор не знает, какой будет метод вызван и вынужден использовать позднее связывание. А в точке (1) компилятор уже во время компиляции знает, что здесь может быть вызван только s1::f, без вариантов. Да, он может сделать этот вызов через механизм позднего связывания. Только зачем? Где тот пункт стандарта, который обязывает его сделать только так?
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 14.08.2025 10:18 rg45 . Предыдущая версия .
Re[26]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 14.08.25 10:25
Оценка:
Здравствуйте, rg45, Вы писали:

R>В точке (3) компилятор не знает, какой будет метод вызван и вынужден использовать позднее связывание. А в точке (1) компилятор уже во время компиляции знает, что здесь может быть вызван только s1::f, без вариантов. Да, он может сделать этот вызов через механизм позднего связывания. Только зачем?


Извините, но вот эта часть мне не интересна от слова совсем.
И, как мне кажется, обсуждаемая глава книги вовсе не про то, что знает компилятор во время компиляции.

PS. Для разнообразия чуть измените конструктор s1:
struct s1 {
  virtual void f() { std::cout << "s1::f" << std::endl; }

  s1() {
    call_f();
  }

  void call_f();
};

// Где-то совсем в другом месте, возможно даже в другой единице трансляции.
void s1::call_f() {
  this->f(); // (3)
}

У нас теперь нет точки (1) в которой компилятор что-то точно знает.
Зато есть точка (3), которая на разных путях к call_f приводит к разному наблюдаемому поведению.
Re[27]: Книжка по UB
От: rg45 СССР  
Дата: 14.08.25 10:45
Оценка:
Здравствуйте, so5team, Вы писали:

S>PS. Для разнообразия чуть измените конструктор s1:

S>
S>struct s1 {
S>  virtual void f() { std::cout << "s1::f" << std::endl; }

S>  s1() {
S>    call_f();
S>  }

S>  void call_f();
S>};

S>// Где-то совсем в другом месте, возможно даже в другой единице трансляции.
S>void s1::call_f() {
  this->>f(); // (3)
S>}
S>

S>У нас теперь нет точки (1) в которой компилятор что-то точно знает.
S>Зато есть точка (3), которая на разных путях к call_f приводит к разному наблюдаемому поведению.

Ну, ладно, сдаюсь

Хотя, может же и проинлайнить
--
Справедливость выше закона. А человечность выше справедливости.
Re[28]: Книжка по UB
От: so5team https://stiffstream.com
Дата: 14.08.25 11:06
Оценка:
Здравствуйте, rg45, Вы писали:

R>Хотя, может же и проинлайнить


С точки зрения описанной в проблемной главе книги ситуации это вообще никакой роли не играет.

А ситуация такая, что в "более лучших"(tm) языках мы можем полагаться на вызов "самой свежей" реализации виртуального метода в конструкторах/деструкторах. И на этом могут быть построенны некоторые проектные решения. Типа того, что есть базовый класс, в конструкторе которого всегда вызывается виртуальный метод preallocate_resources. А производным классам достаточно просто переопределить этот метод у себя не заботясь о том, кто его вызовет (вызовет как раз базовый класс).

Тогда как в С++ этот паттерн не будет работать от слова совсем. С preallocate_resources нужно будет колупаться в конструкторах классов-наследников.

Каким образом при этом в C++ будет происходить вызов preallocate_resources ну вот не важно со слова совсем, если вы обычный пользователь языка.

Если вы плохо знаете C++ или тащите в С++ привычки из "более лучших"(tm) языков, то ждите сюрпризов.

О чем, собственно, в этой проблемной главе и написано.
Re: Книжка по UB
От: student__  
Дата: 14.08.25 11:39
Оценка: -1
Здравствуйте, LaptevVV, Вы писали:

LVV>Рекомендую!


В книге ошибочный посыл прямо во введении:

>Все начинается просто и незатейливо: обычный десятиклассник увлекается программированием, знакомится с алгоритмическими задачками, решения которых должны быть быстрыми. Узнает о языке C++


"алгоритмы должны быть быстрыми" — это неоднозначное выражение. Реализация может быть быстрая. И "быстрота алгоритмов" даже у школьника должна ассоциироваться с ассимптотической сложностью.
"узнаёт о C++" здесь натянуто, как сова на глобус.
Более корректно, как уже тут написали, когда уже опытнйый сишник видит плюсы, и думает, что там всё по уму сделано, а на самом деле...
Re[29]: Книжка по UB
От: rg45 СССР  
Дата: 14.08.25 12:10
Оценка:
Здравствуйте, so5team, Вы писали:

R>>Хотя, может же и проинлайнить


S>С точки зрения описанной в проблемной главе книги ситуации это вообще никакой роли не играет.


Ну, я согласился уже. Есть общие требования к виртуальным функциям, и есть уточнения к этим требования для конструкторов/деструкторов. И эти уточнения не отменяют общие требования, а только уточняют их. А те случаи, когда компилятор может заменить виртуальный вызов на прямой — это следует трактовать как оптимизации, не меняющие поведение. Всё верно?

S>А ситуация такая, что в "более лучших"(tm) языках мы можем полагаться на вызов "самой свежей" реализации виртуального метода в конструкторах/деструкторах.


Да, я в курсе. У них же там нет и детерминированного времени жизни объектов, поэтому могут позволить себе такой беспредел.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 14.08.2025 12:22 rg45 . Предыдущая версия . Еще …
Отредактировано 14.08.2025 12:12 rg45 . Предыдущая версия .
Re[23]: Книжка по UB
От: rg45 СССР  
Дата: 14.08.25 12:15
Оценка:
Здравствуйте, Pzz, Вы писали:

R>>Причем, неотличимы с точки зрения требований стандарта. Отсюда следует, что у нас нет никаких оснований, чтобы утверждать или отрицать "виртуальную диспетчеризацию" в конструкторах и деструкторах. О которой, кстати сказать, в стандарте вообще нет никаких упоминаний. Это термин, расчитанный на интуитивное восприятие. Тут было бы неплохо определиться для начала, о чём мы вообще спорим.


Pzz>По-моему, вы спорите о терминах


Скорее о трактовках и интерпретациях.
--
Справедливость выше закона. А человечность выше справедливости.
Re[2]: Книжка по UB
От: B0FEE664  
Дата: 15.08.25 18:05
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Матерную поэму. В этой поэме должно быть много красивых, выразительных и поэтичных русских матерных слов.


складывать строку с числом — так себе идея.
Со знаковым char есть более простая и вместе с тем коварная проблема. Языка С она тоже касается:

char convert(char ch)
{
    char table[256] = { некоторые значения для перестановки };
    return table[ch];
}


И всё работает до перехода на компилятор, где char (какого-то лешего) знаковый.
И каждый день — без права на ошибку...
Re[3]: Книжка по UB
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 15.08.25 18:18
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>И всё работает до перехода на компилятор, где char (какого-то лешего) знаковый.


char по-моему везде по умолчанию знаковый. Но лично я уже давно привык явно кастовать, в данном случае я кастовал бы так: (size_t)(unsigned char)ch
Маньяк Робокряк колесит по городу
Re[11]: Книжка по UB
От: Лазар Бешкенадзе СССР  
Дата: 19.08.25 07:16
Оценка: -1 :)
Здравствуйте, Pzz, Вы писали:

ЛБ>>

При деструктурировании наоборот — часть объекта-наследника уже уничтожена, и если позволить динамический вызов, можно легко получить use-after-free.


ЛБ>>Человек явно не понимает механизма и считает что при динамическом вызове может быть вызвано что-то из производного класса который уже разобран.


Pzz>Человек явно пишет, что ничего из полуразобранного производного класса вызвано быть не может и объясняет почему.


Pzz>Я удивлён, что ты понимаешь этот текст строго наоборот написанному.


Я боюсь что-то не так прочитал именно ты. Показываю еще раз:

если позволить динамический вызов, можно легко получить use-after-free


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

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

Вот еще один pearl:

Радуйтесь! Это одно из немногих мест в C++, где вас защитили от неопределенного поведения со временами жизни!


Это не защита и к временам жизни этот UB не имеет никакого отношения. UB в этом коде называется "a virtual call to a pure virtual function" а компилятор своей оптимизацией просто замел мусор под коврик.

-
Re[4]: Книжка по UB
От: B0FEE664  
Дата: 22.08.25 15:44
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Мне еще вот такое очень понравилось:

  Скрытый текст
Pzz>
Pzz>// пример взят из блога https://mohitmv.github.io/blog/Shocking-Undefined-Behaviour-In-Action/
Pzz>int main() {
Pzz>  char buf[50] = "y";
Pzz>  for (int j = 0; j < 9; ++j) {
Pzz>    std::cout << (j * 0x20000001) << std::endl;
Pzz>    if (buf[0] == 'x') break;
Pzz>  }
Pzz>}
Pzz>

Pzz>Тут компилятор решает вынести умножение из тела цикла. Заменяет ++j на j += 0x20000001, ну и верхнюю границу, девятку, тоже умножает на 0x20000001. А что, имеет право, целочисленное переполнение для знаковых типов — это UB. В итоге цикл на 9 оборотов становится бесконечным.

Не, мне не нравится. j не должен принимать отрицательные значения, значит он должен быть unsigned. К тому же я не вижу смысла в этом коде.
И каждый день — без права на ошибку...
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.