Здравствуйте, 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 приводит к разному наблюдаемому поведению.
Ну, ладно, сдаюсь
Хотя, может же и проинлайнить
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Хотя, может же и проинлайнить
С точки зрения описанной в проблемной главе книги ситуации это вообще никакой роли не играет.
А ситуация такая, что в "более лучших"(tm) языках мы можем полагаться на вызов "самой свежей" реализации виртуального метода в конструкторах/деструкторах. И на этом могут быть построенны некоторые проектные решения. Типа того, что есть базовый класс, в конструкторе которого всегда вызывается виртуальный метод preallocate_resources. А производным классам достаточно просто переопределить этот метод у себя не заботясь о том, кто его вызовет (вызовет как раз базовый класс).
Тогда как в С++ этот паттерн не будет работать от слова совсем. С preallocate_resources нужно будет колупаться в конструкторах классов-наследников.
Каким образом при этом в C++ будет происходить вызов preallocate_resources ну вот не важно со слова совсем, если вы обычный пользователь языка.
Если вы плохо знаете C++ или тащите в С++ привычки из "более лучших"(tm) языков, то ждите сюрпризов.
О чем, собственно, в этой проблемной главе и написано.
Здравствуйте, LaptevVV, Вы писали:
LVV>Рекомендую!
В книге ошибочный посыл прямо во введении:
>Все начинается просто и незатейливо: обычный десятиклассник увлекается программированием, знакомится с алгоритмическими задачками, решения которых должны быть быстрыми. Узнает о языке C++
"алгоритмы должны быть быстрыми" — это неоднозначное выражение. Реализация может быть быстрая. И "быстрота алгоритмов" даже у школьника должна ассоциироваться с ассимптотической сложностью.
"узнаёт о C++" здесь натянуто, как сова на глобус.
Более корректно, как уже тут написали, когда уже опытнйый сишник видит плюсы, и думает, что там всё по уму сделано, а на самом деле...
Здравствуйте, so5team, Вы писали:
R>>Хотя, может же и проинлайнить
S>С точки зрения описанной в проблемной главе книги ситуации это вообще никакой роли не играет.
Ну, я согласился уже. Есть общие требования к виртуальным функциям, и есть уточнения к этим требования для конструкторов/деструкторов. И эти уточнения не отменяют общие требования, а только уточняют их. А те случаи, когда компилятор может заменить виртуальный вызов на прямой — это следует трактовать как оптимизации, не меняющие поведение. Всё верно?
S>А ситуация такая, что в "более лучших"(tm) языках мы можем полагаться на вызов "самой свежей" реализации виртуального метода в конструкторах/деструкторах.
Да, я в курсе. У них же там нет и детерминированного времени жизни объектов, поэтому могут позволить себе такой беспредел.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Pzz, Вы писали:
R>>Причем, неотличимы с точки зрения требований стандарта. Отсюда следует, что у нас нет никаких оснований, чтобы утверждать или отрицать "виртуальную диспетчеризацию" в конструкторах и деструкторах. О которой, кстати сказать, в стандарте вообще нет никаких упоминаний. Это термин, расчитанный на интуитивное восприятие. Тут было бы неплохо определиться для начала, о чём мы вообще спорим.
Pzz>По-моему, вы спорите о терминах
Скорее о трактовках и интерпретациях.
--
Справедливость выше закона. А человечность выше справедливости.
При деструктурировании наоборот — часть объекта-наследника уже уничтожена, и если позволить динамический вызов, можно легко получить use-after-free.
ЛБ>>Человек явно не понимает механизма и считает что при динамическом вызове может быть вызвано что-то из производного класса который уже разобран.
Pzz>Человек явно пишет, что ничего из полуразобранного производного класса вызвано быть не может и объясняет почему.
Pzz>Я удивлён, что ты понимаешь этот текст строго наоборот написанному.
Я боюсь что-то не так прочитал именно ты. Показываю еще раз:
если позволить динамический вызов, можно легко получить use-after-free
Динамический вызов никто не запрещает и вроде где-то тут ты сам писал об этом.
А вот автор не понимает сути и его "решение" явно заменить виртуальный вызов на статический хотя в некоторых сценариях это может привести к изменению поведения корректно работающей программы.
Вот еще один pearl:
Радуйтесь! Это одно из немногих мест в C++, где вас защитили от неопределенного поведения со временами жизни!
Это не защита и к временам жизни этот UB не имеет никакого отношения. UB в этом коде называется "a virtual call to a pure virtual function" а компилятор своей оптимизацией просто замел мусор под коврик.
Здравствуйте, 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. К тому же я не вижу смысла в этом коде.