Здравствуйте, Pzz, Вы писали:
Pzz>>>Матерную поэму. В этой поэме должно быть много красивых, выразительных и поэтичных русских матерных слов.
S>>Эка вы в адрес своей любимой ламповой сишки раздухарились! Прям любо-дорого.
Pzz>В сишечке это не имеет таких последствий.
Вот объясните мне, а то я не понимаю это что: глупость, двойные стандарты или и то, и другое?
Ибо когда в Си из-за дебильных правил приведения типа хз какой char автоматически и неявно генерируется из double-литерала, то это OK.
А вот когда в С++ в точности тоже самое происходит из-за тех же самых (именно что тех же самых, унаследованных из Си) правил -- то это уже мать-мать-мать-перемать.
Ну вот как это в одной башке уживается?
Pzz>К тому же с каждого, кто рискует программировать на чистом Си, мироздание берет подписку, что все отстреленные ноги за счет стрелка, претензии к безопасности пулемета не принимаются. Что несколько дисциплинирует, по крайней мере, выживших.
Вы, видимо, забыли (а может никогда и не знали) старый афоризм: Си позволяет вам выстрелить себе в ногу, в C++ это сделать сложнее, но когда вы это таки сделаете, то вам отрывает нахрен сразу обе ноги.
Ну и чтобы еще набросить на вашу пустую седую голову: в современном С++ хотя бы можно написать s += char{49.5} и получить ошибку компиляции. А вот что здесь может предложить Си?
Здравствуйте, LaptevVV, Вы писали:
LVV>Рекомендую!
Полистал немного. Об этом надо писать поэму, а не книжку в прозе.
#include <string>
int main() {
std::string s;
s += 48; // неявное приведение к char.
s += 1000; // а тут еще и с переполнением, очень неприятным
// на платформе с signed char.
s += 49.5; // опять-таки неявное приведение к char
}
Матерную поэму. В этой поэме должно быть много красивых, выразительных и поэтичных русских матерных слов.
Здравствуйте, so5team, Вы писали:
Pzz>>Матерную поэму. В этой поэме должно быть много красивых, выразительных и поэтичных русских матерных слов.
S>Эка вы в адрес своей любимой ламповой сишки раздухарились! Прям любо-дорого.
В сишечке это не имеет таких последствий. Си, всё-таки, простой, обозримый, компактный язык. К тому же с каждого, кто рискует программировать на чистом Си, мироздание берет подписку, что все отстреленные ноги за счет стрелка, претензии к безопасности пулемета не принимаются. Что несколько дисциплинирует, по крайней мере, выживших.
Pzz>Там самая мякотка — в главе про неработающий синтаксис.
Pzz>
Pzz>int main() {
Pzz> // ЭТО НЕ ВЫЗОВ КОНСТРУКТОРА!
Pzz> Worker w(Timer()); // предобъявление функции, которая возвращает Worker и принимает функцию, возвращающую Timer и не принимающую ничего!
Pzz> std::cout << w; // имя функции неявно преобразуется к указателю, который неявно преобразуется к bool
Pzz> // будет выведено 1 (true)
Pzz>}
Pzz>
Здравствуйте, 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 оборотов становится бесконечным.
Здравствуйте, LaptevVV, Вы писали:
LVV>Рекомендую!
Валерий Викторович, я очень давно не программировал но в свое время неплохо разбирался в C++98. Я не думаю что в механизме виртуальных функций что-нибудь изменилось с тех пор. Поэтому сообщаю вам что вот в этом разделе написана абсолютная чушь:
И пишут что здесь проблема в том что "в конструкторах и деструкторах в C++ виртуальная диспетчеризация методов не работает". Я повторяю она работает и в стандарте написано как работает. Здесь всего одна проблема — виртуальный вызов чисто виртуальной функции. Вот это UB. И как раз об этом там ни слова.
Здравствуйте, LaptevVV, Вы писали:
ЛБ>>А вы мне советуете выбросить на ветер 1140 рублей. Нехорошо. LVV>Ну, ничего такого я не советовал. LVV>Я рекомендовал книжку почитать. LVV>Вот вы почитали, и пост родился. Другие заинтересуются...
Причём забесплатно, небось, почитал. А ты ему, выходит, теперь 1140 рублей должен.
Здравствуйте, Лазар Бешкенадзе, Вы писали:
ЛБ>Вот ты говоришь здесь есть виртуальная диспетчеризация а здесь ее нет а я не видел таких различий в стандарте, ты "выше даешь определение виртуальности," а я не видел такого определения в стандарте.
А книжка не текст стандарта обсуждает.
ЛБ>В стандарте делается различие между виртуальным вызовом и статическим, а твоих определений там нет.
И я не текст стандарта обсуждаю.
ЛБ>И под виртуальной диспетчеризацией я понимаю
Мне фиолетово что вы под виртуальной диспетчеризацией понимаете, т.к. речь идет не о вас, а о том, что написано в обсуждаемой книге. А там написано о сюрпризах, которые могут поджидать людей, по незнанию предполагающих, что в C++ вызов виртуального метода в конструкторе/деструкторе будет вести себя так же, как вызов оного метода в любом другом месте, и что в C++ будет совсем другое поведение, нежели во многих других языках с поддержкой ООП -- Java, C#, Ruby, Python). О чем автор буквально и пишет:
В конструкторах и деструкторах в C++ виртуальная диспетчеризация методов не работает (В других языках — например, в C# или Java — наоборот, что доставляет свои проблемы).
PS. Мне, блин, сложно понять почему кто-то доколупался к этому разделу книги. Я сам в свое время наступил на те же грабли. И с другими языками поработал, в которых поведение виртуальных методов в конструкторе отличное от C++ного. Поэтому то, что написано в этом разделе воспринимается просто, понятно и достоверно. А желающие доколупаться до букв идут лесом.
Здравствуйте, rg45, Вы писали:
S>>>То, что это к виртуальной диспетчеризации нормального человека не имеет отношения -- это уже другой вопрос. Но ведь работает же
M>>Это тоже виртуальная диспетчеризация
R>Ну, хорошо. Виртуальная диспетчеризация работает, но её результат неотличим от прямого невиртуального вызова. Нет возражений против такой формулировки?
R>Так а стоп. А почему тогда мы уверены, что она работает? В чём это проявляется и как это можно проверить?
Пришел к выводу, что причина спора в этом обсуждении в том, что часть участников (Лазар, Marty, watchmaker) придерживаются той точки зрения, что механизм виртуальной диспетчеризации работает потому, что он работает именно так, как описано в стандарте. Т.е. если в программе происходит именно то, что и следует по стандарту языка C++, то значит все работает и работает так, как надо.
Тогда как автор книги и ваш покорный слуга придерживаемся той точки зрения, что есть неопытный пользователь языка C++, который допускает неожиданную для себя ошибку из-за своих ожиданий о том, как работает виртуальный вызов с точки зрения пользователя (подкрепленной, возможно, опытом из других языков программирования).
Т.е. есть как бы "виртуальный вызов с точки зрения общепринятых ожиданий" и есть "виртуальный вызов с точки зрения языка C++".
Поэтому одна группа (в которой я) говорит о том, что в конструкторе виртуальности нет.
А другая группа, говорит о том, что она есть (опираясь на стандарт C++).
Суть же в том, что проблемная глава ориентирована не на знатоков стандарта.
При деструктурировании наоборот — часть объекта-наследника уже уничтожена, и если позволить динамический вызов, можно легко получить use-after-free.
ЛБ>>Человек явно не понимает механизма и считает что при динамическом вызове может быть вызвано что-то из производного класса который уже разобран.
Pzz>Человек явно пишет, что ничего из полуразобранного производного класса вызвано быть не может и объясняет почему.
Pzz>Я удивлён, что ты понимаешь этот текст строго наоборот написанному.
Я боюсь что-то не так прочитал именно ты. Показываю еще раз:
если позволить динамический вызов, можно легко получить use-after-free
Динамический вызов никто не запрещает и вроде где-то тут ты сам писал об этом.
А вот автор не понимает сути и его "решение" явно заменить виртуальный вызов на статический хотя в некоторых сценариях это может привести к изменению поведения корректно работающей программы.
Вот еще один pearl:
Радуйтесь! Это одно из немногих мест в C++, где вас защитили от неопределенного поведения со временами жизни!
Это не защита и к временам жизни этот UB не имеет никакого отношения. UB в этом коде называется "a virtual call to a pure virtual function" а компилятор своей оптимизацией просто замел мусор под коврик.
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 — ничего не пишет!
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, 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)
}
Здравствуйте, LaptevVV, Вы писали:
LVV>Ну, я как привык смолоду писать попроще, через равенство — так и пишу. LVV>Сейчас приходится переучиваться на {} LVV>Но вообще — да.
Это я выдернул первое, что под руку подвернулось. Там много весёлого.
Я понимаю, что половина этого в Си тоже есть. Но в Си нет всех этих прекрасных автоматизмов, которые способны превратить простую и понятную ошибку в хитрую или запутанную.
Здравствуйте, so5team, Вы писали:
S>Ну и чтобы еще набросить на вашу пустую седую голову: в современном С++ хотя бы можно написать s += char{49.5} и получить ошибку компиляции. А вот что здесь может предложить Си?
Мы с вами уже договаривались, что либо вы не хамите, либо мы не общаемся.
Здравствуйте, LaptevVV, Вы писали:
LVV>Рекомендую!
Вспомнилось. Во времена Фортрана литературы "Грабли в Фортране" было полно. Мы даже что-то проверяли, когда учились. Порчу константы или беспорядочное использование COMMON, особенно безымянных. Зато в реальной работе граблей избегать удавалось.
А на C/C++ мне довелось написать совсем немного кода. Всякие там JNI и вставки к фортрановскому проекту.
Но судя по опыту работы с Фортраном, такая литература должна быть полезной.
Здравствуйте, LaptevVV, Вы писали:
LVV>Она очень полезна.
В C/C++ хотя бы зарезервированные слова есть. В Фортране и этого не было. Да ещё он пробелы игнорировал.
LVV>Для начинающих — откровение увидеть, как сложение 2 положительных становится отрицательным.
Нас этому на ТОЭ для программистов учили, помню. Ещё, кстати, начинающих повергает в шок, что целые числа не являются подмножеством вещественных, а это существенно различные объекты, когда речь идёт о машинных вычислениях.
LVV>Неявные преобразования — зло абсолютное.
Повбывав бы.
LVV>Теперь у меня для студентов есть аргументированный источник примеров. LVV>Не совсем уж тривиальных.
Да не только для студентов. Я вот ни разу не плюсовик, но иногда, довольно редко, пригодится и сейчас его брать. Всякие мелкие плагины делать. Шанс нарваться на грабли весьма высок.
Здравствуйте, Marty, Вы писали:
M>Подожди. Если в текущем наследнике виртуальный метод не переопределён, то что ты собрался вызывать невиртуально?
Вот, смотри, как раз этот случай. Здесь c2::f — она же с3::f, она же с4::f. Т.е. функция, определённая в базовом классе c2::f, может быть вызвана в наследнике невиртуальным способом с использование имени класса наследника — с4::f. Так работает наследование.
#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{};
}
Ещё одним важным моментом является тот факт, что при вызове виртуальной функции в конструкторе или деструкторе, компилятор знает, какой нужно дёрнуть метод прямо во время компиляции! И на кой чёрт, спрашивается, вызывать этот метод через дополнительную косвенность, когда это можно сделать напрямую?
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, LaptevVV, Вы писали:
LVV>Рекомендую!
В книге ошибочный посыл прямо во введении:
>Все начинается просто и незатейливо: обычный десятиклассник увлекается программированием, знакомится с алгоритмическими задачками, решения которых должны быть быстрыми. Узнает о языке C++
"алгоритмы должны быть быстрыми" — это неоднозначное выражение. Реализация может быть быстрая. И "быстрота алгоритмов" даже у школьника должна ассоциироваться с ассимптотической сложностью.
"узнаёт о C++" здесь натянуто, как сова на глобус.
Более корректно, как уже тут написали, когда уже опытнйый сишник видит плюсы, и думает, что там всё по уму сделано, а на самом деле...
Здравствуйте, Pzz, Вы писали:
Pzz>Полистал немного. Об этом надо писать поэму, а не книжку в прозе.
Да там много интересного.
Вот грабли, на которые я ещё не наступал:
Pzz>Там самая мякотка — в главе про неработающий синтаксис. Pzz>
...
Pzz> // ЭТО НЕ ВЫЗОВ КОНСТРУКТОРА!
Pzz> Worker w(Timer()); // предобъявление функции, которая возвращает Worker и принимает функцию, возвращающую Timer и не принимающую ничего!
Pzz>
Ну, я как привык смолоду писать попроще, через равенство — так и пишу.
Сейчас приходится переучиваться на {}
Но вообще — да.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Это ещё с сишечки по-моему так. В любом случае, компилятор тебе скажет, как минимум предупреждение, что одна переменная скрывает другую. Но, конечно, если у тебя при компиляции "кучи ненужных варнингов" (c) (tm), но будут грабли.
Здравствуйте, Pzz, Вы писали:
S>>Эка вы в адрес своей любимой ламповой сишки раздухарились! Прям любо-дорого.
Pzz>В сишечке это не имеет таких последствий. Си, всё-таки, простой, обозримый, компактный язык. К тому же с каждого, кто рискует программировать на чистом Си, мироздание берет подписку, что все отстреленные ноги за счет стрелка, претензии к безопасности пулемета не принимаются. Что несколько дисциплинирует, по крайней мере, выживших.
А какие последствия будут в приведённом случае? К строке прибавится символ? Чем это отличается от сишечного?
Здравствуйте, Marty, Вы писали:
Pzz>>В сишечке это не имеет таких последствий. Си, всё-таки, простой, обозримый, компактный язык. К тому же с каждого, кто рискует программировать на чистом Си, мироздание берет подписку, что все отстреленные ноги за счет стрелка, претензии к безопасности пулемета не принимаются. Что несколько дисциплинирует, по крайней мере, выживших.
M>А какие последствия будут в приведённом случае? К строке прибавится символ? Чем это отличается от сишечного?
Здесь — ничем.
А вот когда к сишным (весма вольным и дурным) правилам преобразования типов добавляется сипплюсплюсная идея вложить в тип глубокий смысл, позволив через оверлоадинг приделать к разным типам разное поведение для одинаково выглядещих действий, жизнь становится уже весьма забавной.
Здравствуйте, Pzz, Вы писали:
M>>А какие последствия будут в приведённом случае? К строке прибавится символ? Чем это отличается от сишечного?
Pzz>Здесь — ничем.
Pzz>А вот когда к сишным (весма вольным и дурным) правилам преобразования типов добавляется сипплюсплюсная идея вложить в тип глубокий смысл, позволив через оверлоадинг приделать к разным типам разное поведение для одинаково выглядещих действий, жизнь становится уже весьма забавной.
Дурные правила преобразования типов работают только для интегральных типов и плавучки. Это полностью наследие сишечки. Для типов, в которые вложен глубокий смысл, всё весьма строго.
P>Вспомнилось. Во времена Фортрана литературы "Грабли в Фортране" было полно. Мы даже что-то проверяли, когда учились. Порчу константы или беспорядочное использование COMMON, особенно безымянных. Зато в реальной работе граблей избегать удавалось.
Да, это я тоже знаю... P>А на C/C++ мне довелось написать совсем немного кода. Всякие там JNI и вставки к фортрановскому проекту. P>Но судя по опыту работы с Фортраном, такая литература должна быть полезной.
Она очень полезна.
Для начинающих — откровение увидеть, как сложение 2 положительных становится отрицательным.
А в этой книжке поглубже, для мидлов.
Неявные преобразования — зло абсолютное.
Теперь у меня для студентов есть аргументированный источник примеров.
Не совсем уж тривиальных.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, 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):
Здравствуйте, Лазар Бешкенадзе, Вы писали:
ЛБ>Валерий Викторович, я очень давно не программировал но в свое время неплохо разбирался в C++98. Я не думаю что в механизме виртуальных функций что-нибудь изменилось с тех пор. Поэтому сообщаю вам что вот в этом разделе написана абсолютная чушь:
ЛБ>https://github.com/Nekrolm/ubbook/blob/master/runtime/virtual_functions.md
ЛБ>Человек пишет о том в чем не разобрался и судя по всему даже не заглядывал в текст стандарта.
Здравствуйте, so5team, Вы писали:
S>И что конкретно там не так?
Все. Там написано:
В конструкторах и деструкторах в C++ виртуальная диспетчеризация методов не работает
Это чушь. Работает.
Единственная проблема в приведенном коде — вызов чисто виртуальной функции в ctor/dtor. Убери в этом коде =0 при описании виртуальных функций и их можно вызывать в ctor/dtor.
ЛБ>Единственная проблема в приведенном коде — вызов чисто виртуальной функции в ctor/dtor. Убери в этом коде =0 при описании виртуальных функций и их можно вызывать в ctor/dtor.
указатель на виртуальную ф-ию прописывается при вызове конструктора. вызывая виртуальную ф-ию из конструктора или деструктора человек непонятно чего собирается добиться.
Здравствуйте, 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 проверял.
Здравствуйте, Лазар Бешкенадзе, Вы писали:
ЛБ>Здравствуйте, 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, ...
Тогда какие претензии к главе статьи? Там именно об этом речь и идет. И мой пример именно это и демонстрирует.
В конструкторах и деструкторах в C++ виртуальная диспетчеризация методов не работает
ЛБ>Это чушь. Работает.
ЛБ>Единственная проблема в приведенном коде — вызов чисто виртуальной функции в ctor/dtor. Убери в этом коде =0 при описании виртуальных функций и их можно вызывать в ctor/dtor.
Вызвать-то можно, весь вопрос только в том, что из этого получится. Некоторые ошибочно ожидают, что будет вызвана версия виртуальной функции из наследника, а вызвается либо собственная, либо вообще версия из базового класса, если собственной не предоставлено. Этим же объясняется, почему вообще возможeн pure virtual call.
#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"
}
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, so5team, Вы писали:
S>Тогда какие претензии к главе статьи? Там именно об этом речь и идет. И мой пример именно это и демонстрирует.
Там про UB. Там написано не работает. А на самом деле работает и никакого UB от этого нет. UB там по другой причине — вызов pure virtual function в ctor/dtor. И как раз об этом автор ничего не говорит.
Здравствуйте, Лазар Бешкенадзе, Вы писали:
ЛБ>Там написано не работает. А на самом деле работает
И как же оно работает, если при вызове виртуального метода, у которого есть новая версия в производном классе, вызывается все-таки версия из базового класса?
Ведь виртуальность в том и состоит, что мы дергаем метод через указатель/ссылку на базовый класс, а получаем вызов кода из производного класса. Но в конструкторах/деструкторах именно этого-то эффекта и нет.
Именно об этом автор книги и пишет русским по белому:
В конструкторах и деструкторах в C++ виртуальная диспетчеризация методов не работает
Здравствуйте, so5team, Вы писали:
S>И как же оно работает, если при вызове виртуального метода, у которого есть новая версия в производном классе, вызывается все-таки версия из базового класса?
Вот до определенного уровня — до класса now under construction и работает. Если у тебя там 10 базовых классов и ты сделал override только во втором то этот override и будет вызван.
Здравствуйте, Лазар Бешкенадзе, Вы писали:
ЛБ>Вот до определенного уровня — до класса now under construction и работает.
Боюсь показаться грубым, но слово "работает" в данном случае не подходит. Тут как со второй свежестью.
Виртуальная диспетчеризация либо работает, либо нет.
Здравствуйте, Лазар Бешкенадзе, Вы писали:
ЛБ>Вот до определенного уровня — до класса now under construction и работает. Если у тебя там 10 базовых классов и ты сделал override только во втором то этот override и будет вызван.
Тут вот какая штука. В данном примере, вызов виртуальный фунции stop() в деструкторе ЗАВЕДОМО будет эквивалентен невиртуальной форме вызова: Processor::stop. Как при этом можно говорить о работоспособности виртуальной диспетчеризации? Устройство виртуальных механизмов стандарт языка ведь никак не регламентирует и в разных имплементациях языка он может быть разным.
--
Справедливость выше закона. А человечность выше справедливости.
ЛБ>Валерий Викторович, я очень давно не программировал но в свое время неплохо разбирался в C++98. Я не думаю что в механизме виртуальных функций что-нибудь изменилось с тех пор. Поэтому сообщаю вам что вот в этом разделе написана абсолютная чушь: ЛБ>https://github.com/Nekrolm/ubbook/blob/master/runtime/virtual_functions.md ЛБ>Человек пишет о том в чем не разобрался и судя по всему даже не заглядывал в текст стандарта.
Ну, выше уже знатоки расписали... ЛБ>А вы мне советуете выбросить на ветер 1140 рублей. Нехорошо.
Ну, ничего такого я не советовал.
Я рекомендовал книжку почитать.
Вот вы почитали, и пост родился. Другие заинтересуются...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, Лазар Бешкенадзе, Вы писали:
ЛБ>И пишут что здесь проблема в том что "в конструкторах и деструкторах в C++ виртуальная диспетчеризация методов не работает". Я повторяю она работает и в стандарте написано как работает. Здесь всего одна проблема — виртуальный вызов чисто виртуальной функции. Вот это UB. И как раз об этом там ни слова.
Какие у тебя есть основания утверждать, что виртуальная диспетчеризация работает, когда вызов stop() ЗАВЕДОМО эквивалентен невиртуальному вызову Processor::stop()?
"Можно вызвать" не то же самое, что "работает виртуальная диспетчеризация".
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, 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() */ }
};
ИМХО, именно этот сценарий Лазар и пытается нам описать.
То, что это к виртуальной диспетчеризации нормального человека не имеет отношения -- это уже другой вопрос. Но ведь работает же
Здравствуйте, Лазар Бешкенадзе, Вы писали:
ЛБ>Вызов stop() в этом тексте — UB.
Нет там такого утверждения.
Возможно, в тексте не хватает чёткости формулировок, что приводит к разночтениям. Как по мне, то смысл написанного как раз в том, что UB является результатом pure virtual call. И объясняется, откуда этот pure virtual call берётся.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, so5team, Вы писали:
ЛБ>>Вот до определенного уровня — до класса now under construction и работает.
S>И в конструкторах/деструкторах она не работает.
Тогда, если быть последовательным, придётся принять утверждение: "в final-классах виртуальная диспетчеризация не работает". Как думаешь, хорошее и полезное это утверждение, чтобы его запоминать?
Ведь когда компилятор встречает ссылку на final-класс, то он знает, что динамический тип и статический тип совпадает, и вызовы виртуальных функций работают по тем же правилам, по которым они работают в конструкторах и деструкторах (в них совпадение гарантированно), и из этого проистекает вся специфика таких вызовов.
А ключевое слово final распространяет это правило и на другие случаи.
S>Виртуальная диспетчеризация либо работает, либо нет.
А постоянно мигающий жёлтым светофор на перекрёстке "работает" или нет?
В бытовом смысле — нет. А по ПДД — работает.
В статье используется отвратительно неоднозначная терминология.
Если бы было написано, что конкретно подразумевается под "не работает", то с этим ещё можно было бы как-то смириться.
Но сейчас там предлагается додумывать самому. А так как есть несколько хороших взаимноисключающих трактовок, то я вполне согласен с тем, что в статье написано некорректное утверждение — ведь оно в любом случае будет противоречить каким-то из них.
Здравствуйте, watchmaker, Вы писали:
W>Тогда, если быть последовательным, придётся принять утверждение: "в final-классах виртуальная диспетчеризация не работает". Как думаешь, хорошее и полезное это утверждение, чтобы его запоминать?
ИМХО, вполне приемлемая интерпретация, если ориентироваться на наблюдаемое поведение. У нас же нет даже способа доказать или опровергнуть использование виртуального вызова. Можно, конечно, заглянуть в сгенерированный машинный код какого-нибудь компилятора, но обобщить свои выводы мы всё равно не сможем.
--
Справедливость выше закона. А человечность выше справедливости.
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, ...
Ну да. Позовётся виртуальная функция, соответствующая тому уровню создания объекта, к которому относится конструктор. А вот когда объект создастся до конца (т.е., не в конструкторе), то та же виртуальная функция будет соответствовать самому "верхнему" производному классу, который сподобился ее переопределить.
Стандарт и книжка говорят об одном.
Надеюсь хоть, деструктор ведет себя симметрично. Хотя кто его знает, чего ждать от этого плюсплюса.
Здравствуйте, rg45, Вы писали:
ЛБ>>Вызов stop() в этом тексте — UB.
R>Нет там такого утверждения.
Это мое утверждение о приведенном коде — текст здесь надо понимать как текст программы.
R>Возможно, в тексте не хватает чёткости формулировок, что приводит к разночтениям. Как по мне, то смысл написанного как раз в том, что UB является результатом pure virtual call. И объясняется, откуда этот pure virtual call берётся.
Вот что написано:
При деструктурировании наоборот — часть объекта-наследника уже уничтожена, и если позволить динамический вызов, можно легко получить use-after-free.
Человек явно не понимает механизма и считает что при динамическом вызове может быть вызвано что-то из производного класса который уже разобран.
Здравствуйте, 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() из наследников.
Т.е. в данном случае (т.е. внутри конструктора) виртуальности у вызова нет. ЧТД.
Здравствуйте, Marty, Вы писали:
S>>То, что это к виртуальной диспетчеризации нормального человека не имеет отношения -- это уже другой вопрос. Но ведь работает же
M>Это тоже виртуальная диспетчеризация
Если это "виртуальная диспетчеризация", то что же тогда в Java или в Ruby, или в Python? Супервиртуальная или как?
Здравствуйте, so5team, Вы писали:
S>>>То, что это к виртуальной диспетчеризации нормального человека не имеет отношения -- это уже другой вопрос. Но ведь работает же
M>>Это тоже виртуальная диспетчеризация
S>Если это "виртуальная диспетчеризация", то что же тогда ...?
Вот ты говоришь здесь есть виртуальная диспетчеризация а здесь ее нет а я не видел таких различий в стандарте, ты "выше даешь определение виртуальности," а я не видел такого определения в стандарте.
В стандарте делается различие между виртуальным вызовом и статическим, а твоих определений там нет.
И под виртуальной диспетчеризацией я понимаю механизм виртуального вызова виртуальных функций. Именно виртуального вызова потому что виртуальные фунции можно вызывать и статически (с явной квалификацией). Вот этот механизм в стандарте описан в том числе для вызова в ctor/dtor в параграфе часть которого я процитировал выше.
Здравствуйте, Marty, Вы писали:
S>>То, что это к виртуальной диспетчеризации нормального человека не имеет отношения -- это уже другой вопрос. Но ведь работает же
M>Это тоже виртуальная диспетчеризация
Ну, хорошо. Виртуальная диспетчеризация работает, но её результат неотличим от прямого невиртуального вызова. Нет возражений против такой формулировки?
Так а стоп. А почему тогда мы уверены, что она работает? В чём это проявляется и как это можно проверить?
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, so5team, Вы писали:
S>Пришел к выводу, что причина спора в этом обсуждении в том, что часть участников (Лазар, Marty, watchmaker) придерживаются той точки зрения, что механизм виртуальной диспетчеризации работает потому, что он работает именно так, как описано в стандарте. Т.е. если в программе происходит именно то, что и следует по стандарту языка C++, то значит все работает и работает так, как надо.
Так стандарт же описывает каким должно быть поведение программы, а не детали реализации этого поведения. И единиственный вывод, который можно сделать из стандарта как раз тот самый — что вызов виртуальных функции из конструкторов/деструкторов даёт тот же эффект, что и прямой невиртуальный вызов.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, 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, но именно эта версия и будет вызвана.
Здравствуйте, 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. Т.е. даже в таком случае выходит, что эти вызовы неотличимы:
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 ошибка уйдёт, но, опять же, от обоих вызовов — виртуального и невиртуального получим одинаковый эффект.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Так а стоп. А почему тогда мы уверены, что она работает?
Потому что работает так как описано в стандарте.
R>В чём это проявляется и как это можно проверить?
Я не обязан проверять правильность работы иначе как сравнением с тем что написано в стандарте.
Но я подозреваю что ты интересуешься тем как таки определить разницу между виртуальным и статическим вызовом.
1. Если это к тому же чисто виртуальная функция то с виртуальным вызовом налетаем на pure virtual function call
2. Если это обычная виртуальная функция то возможны два варианта:
a. компилятор сделал оптимизацию и заменил виртуальный вызов на статический — здесь разницы нет
b. компилятор не делал такой оптимизации — виртуальный вызов медленнее статического и можно измерить разницу в скорости
Здравствуйте, rg45, Вы писали:
R>Если добавить спецификацю доступа public в с2 ошибка уйдёт, но, опять же, от обоих вызовов — виртуального и невиртуального получим одинаковый эффект.
Тут моя ошибка, нужно было f() описывать в public-части. Давайте считать, что f() доступен для вызова.
Поинт в том, что с точки зрения C++ в конструкторе с4 мы автоматически получаем вызов самой свежей версии f на данный момент.
Это происходит за счет того, что С++ корректно модифицирует таблицу виртуальных методов по мере конструирования класса.
А значит, с формальной точки зрения, виртуальная диспетчеризация работает.
Повторюсь: суть в том, что ожидания неопытного пользователя о поведении виртуальной диспетчеризации в конструкторах/деструкторах, принципиально не совпадают с суровой реальностью стандарта С++
Здравствуйте, Лазар Бешкенадзе, Вы писали:
R>>Так а стоп. А почему тогда мы уверены, что она работает?
ЛБ>Потому что работает так как описано в стандарте.
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) Результат такого вызова будт точно таким же, как если бы мы позвали эту фунцию невиртуальным способом. Никаких утверждений о виртуальной диспетчеризации здесь нет.
--
Справедливость выше закона. А человечность выше справедливости.
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>А значит, с формальной точки зрения, виртуальная диспетчеризация работает.
Ну, это никак не проверяется через поведение программы. Результаты же идентичны.
--
Справедливость выше закона. А человечность выше справедливости.
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>Никаких утверждений о виртуальной диспетчеризации здесь нет.
Я и не писал что здесь есть такие утверждения.
Ты спросил как проверить — я тебе вежливо ответил. Ты юлишь. Я и с тобой прощаюсь.
Здравствуйте, rg45, Вы писали:
S>>А значит, с формальной точки зрения, виртуальная диспетчеризация работает.
R>Ну, это никак не проверяется через поведение программы. Результаты же идентичны.
Почему не проверяется? Согласно стандарту при вызове f() в конструкторе c4 мы получаем вызов наиболее свежей к этому времени версии f -- c2::f.
Значит все работает.
Аналогичная точка зрения у моих "оппонентов" и на ситуацию:
В деструкторе c1 будет вызвана наиболее свежая к этому времени версия f -- c1::f, а не c3::f.
В точности согласно стандарта, а значит все работает как и предписано. А значит все работает.
Т.е. работа "по стандарту" вполне себе проверяется через работу программы.
Вынужден повториться, но проблема здесь не в стандарте или возможности проверить работу согласно стандарта. А в том, что у части пользователей C++ другие ожидания от вызова виртуального метода в конструкторе/деструкторе.
S>Почему не проверяется? Согласно стандарту при вызове f() в конструкторе c4 мы получаем вызов наиболее свежей к этому времени версии f -- c2::f. S>Значит все работает.
Ну так, если мы сделаем невиртуальный вызов: с4::f() мы получим тот же самый вызов c2::f.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
S>>Почему не проверяется? Согласно стандарту при вызове f() в конструкторе c4 мы получаем вызов наиболее свежей к этому времени версии f -- c2::f. S>>Значит все работает.
R>Ну так, если мы сделаем невиртуальный вызов: с4::f() мы получим тот же самый вызов c2::f.
Так ведь это здесь не при чем, вот в чем фокус.
Есть контекст -- конструктор c4.
В нем вызывается виртуальный метод f.
Стандарт описывает какая именно версия f будет вызвана в этом контексте.
Мы этот вызов и получаем. ЧТД.
А то, что в этом контексте вызов this->f() не будет отличаться от вызова c2::f(), ну так это стечение обстоятельств.
Здравствуйте, so5team, Вы писали:
S>Есть контекст -- конструктор c4. S>В нем вызывается виртуальный метод f. S>Стандарт описывает какая именно версия f будет вызвана в этом контексте. S>Мы этот вызов и получаем. ЧТД.
S>А то, что в этом контексте вызов this->f() не будет отличаться от вызова c2::f(), ну так это стечение обстоятельств.
Давай пока забудем про вызов c2::f(). Мы рассматриваем конструктор класса f4 и для этого класса у нас есть два способа вызвать функцию f: 1) виртуальный — f() или this->f() и 2) невиртуальный — f4::f(). И согласно требованиям стандарта оба эти вызова должны дать один и тот же результат. Таким образом, на уровне поведения программы эти два способа неотличимы.
На всякий случай: я не утверждаю, что виртуальный вызов не выполняется. С точки зрения поведения программы это так же недоказуемо, как и неопровергаемо.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Давай пока забудем про вызов c2::f(). Мы рассматриваем конструктор класса c4 и для этого класса у нас есть два способа вызвать функцию f: 1) виртуальный — f() или this->f() и 2) невиртуальный — c4::f(). И согласно требованиям стандарта оба эти вызова должны дать один и тот же результат. Таким образом, на уровне поведения программы эти два способа неотличимы.
Здравствуйте, so5team, Вы писали:
S>Здравствуйте, rg45, Вы писали:
R>>Давай пока забудем про вызов c2::f(). Мы рассматриваем конструктор класса c4 и для этого класса у нас есть два способа вызвать функцию f: 1) виртуальный — f() или this->f() и 2) невиртуальный — c4::f(). И согласно требованиям стандарта оба эти вызова должны дать один и тот же результат. Таким образом, на уровне поведения программы эти два способа неотличимы.
S>Неотличимы. И что из этого должно следовать?
Причем, неотличимы с точки зрения требований стандарта. Отсюда следует, что у нас нет никаких оснований, чтобы утверждать или отрицать "виртуальную диспетчеризацию" в конструкторах и деструкторах. О которой, кстати сказать, в стандарте вообще нет никаких упоминаний. Это термин, расчитанный на интуитивное восприятие. Тут было бы неплохо определиться для начала, о чём мы вообще спорим.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Причем, неотличимы с точки зрения требований стандарта. Отсюда следует, что у нас нет никаких оснований, чтобы утверждать или отрицать "виртуальную диспетчеризацию" в конструкторах и деструкторах. О которой, кстати сказать, в стандарте вообще нет никаких упоминаний. Это термин, расчитанный на интуитивное восприятие. Тут было бы неплохо определиться для начала, о чём мы вообще спорим.
Так я же не зря озвучил то определение, которым пользуюсь в рассуждениях: "виртуальность в том и состоит, что мы дергаем метод через указатель/ссылку на базовый класс, а получаем вызов кода из производного класса". Уверен, что автор книги подразумевал тоже самое.
Именно это определение и нарушается, когда мы рассматриваем ситуацию с вызовом виртуальных методов в конструкторах/деструкторах. В С++.
В других "более лучших"(tm) языках оно-то и не нарушается.
Здравствуйте, so5team, Вы писали:
S>Так я же не зря озвучил то определение, которым пользуюсь в рассуждениях: "виртуальность в том и состоит, что мы дергаем метод через указатель/ссылку на базовый класс, а получаем вызов кода из производного класса". Уверен, что автор книги подразумевал тоже самое.
Так и как можно проверить, каким образом дёрнули метод? Разве что, заглянуть в сгенерированный код. Ну так, во-первых, это знание неправомерно будет обощать на все компиляторы/платформы/конфигурации. А во-вторых, оптимизатор имеет полное право заменить виртуальный вызов на невиртуальный в конструкторе/деструкторе. Он ведь знает, какой нужно дёрнуть метод прямо во время компиляции!
S>Именно это определение и нарушается, когда мы рассматриваем ситуацию с вызовом виртуальных методов в конструкторах/деструкторах. В С++.
Если не трудно, можно ещё раз указать пункт, который нарушается?
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
S>>>То, что это к виртуальной диспетчеризации нормального человека не имеет отношения -- это уже другой вопрос. Но ведь работает же
M>>Это тоже виртуальная диспетчеризация
R>Ну, хорошо. Виртуальная диспетчеризация работает, но её результат неотличим от прямого невиртуального вызова. Нет возражений против такой формулировки?
R>Так а стоп. А почему тогда мы уверены, что она работает? В чём это проявляется и как это можно проверить?
Подожди. Если в текущем наследнике виртуальный метод не переопределён, то что ты собрался вызывать невиртуально?
Здравствуйте, rg45, Вы писали:
R>Причем, неотличимы с точки зрения требований стандарта. Отсюда следует, что у нас нет никаких оснований, чтобы утверждать или отрицать "виртуальную диспетчеризацию" в конструкторах и деструкторах. О которой, кстати сказать, в стандарте вообще нет никаких упоминаний. Это термин, расчитанный на интуитивное восприятие. Тут было бы неплохо определиться для начала, о чём мы вообще спорим.
По-моему, вы спорите о терминах
Я бы сказал, что суть виртуальных методов заключается в том, что производный класс может подменить некоторые методы базового класса, никак с этим базовым классом не договариваясь.
Такое вот динамические связывание.
А как это сделано, через указатель на функцию, или через указатель на таблицу функций, или просто компилятор умный и всегда правильно догадывается, это уже вообще детали реализации.
И поскольку это связывание динамическое, то вполне логично, что в процессе конструирования объекта с виртуальными методами связывание этих методов может меняться.
А дальше идёт простая оптимизация: пока отрабатывается конструктор базового класса, компилятор знает, что производный класс еще никто не создал, и может девиртуализировать виртуальный метод, позвав его напрямую. А может, собственно, этого и не делать, а позвать через указатель.
При всём при том, мне было бы удивительно, если бы еще на этапе конструирования базового класса виртуальные методы уже уходили бы в производный класс. Там к такому повороту судьбы могут быть еще не готовы, ведь тамошний конструктор еще не отработал.
Что конечно порождает, на взгляд новичка, некоторую путаницу. Но видимо, причина этой путаницы состоит не в неудачной реализации в плюсах, а в том, что сама по себе концепция виртуальных методов достаточно сложна и не допускает простую реализацию (хотя если реализовать ее по-сишному, в виде калбеков в структуре, которые "производная" структура может переключить на себя, всё это и не кажется какой-то непонятной магией).
Здравствуйте, rg45, Вы писали:
R>Так и как можно проверить, каким образом дёрнули метод?
А нам не важно как именно дернули -- через обращение к vtable или путем прямого вызова.
Нам важно какой метод в итоге был вызван. Т.е. конкретный механизм вызова всего лишь скучная деталь реализации.
S>>Именно это определение и нарушается, когда мы рассматриваем ситуацию с вызовом виртуальных методов в конструкторах/деструкторах. В С++.
R>Если не трудно, можно ещё раз указать пункт, который нарушается?
В точке (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) получим одинаковое поведение.
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, без вариантов. Да, он может сделать этот вызов через механизм позднего связывания. Только зачем? Где тот пункт стандарта, который обязывает его сделать только так?
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, 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 приводит к разному наблюдаемому поведению.
Здравствуйте, 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) языков, то ждите сюрпризов.
О чем, собственно, в этой проблемной главе и написано.
Здравствуйте, so5team, Вы писали:
R>>Хотя, может же и проинлайнить
S>С точки зрения описанной в проблемной главе книги ситуации это вообще никакой роли не играет.
Ну, я согласился уже. Есть общие требования к виртуальным функциям, и есть уточнения к этим требования для конструкторов/деструкторов. И эти уточнения не отменяют общие требования, а только уточняют их. А те случаи, когда компилятор может заменить виртуальный вызов на прямой — это следует трактовать как оптимизации, не меняющие поведение. Всё верно?
S>А ситуация такая, что в "более лучших"(tm) языках мы можем полагаться на вызов "самой свежей" реализации виртуального метода в конструкторах/деструкторах.
Да, я в курсе. У них же там нет и детерминированного времени жизни объектов, поэтому могут позволить себе такой беспредел.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Pzz, Вы писали:
R>>Причем, неотличимы с точки зрения требований стандарта. Отсюда следует, что у нас нет никаких оснований, чтобы утверждать или отрицать "виртуальную диспетчеризацию" в конструкторах и деструкторах. О которой, кстати сказать, в стандарте вообще нет никаких упоминаний. Это термин, расчитанный на интуитивное восприятие. Тут было бы неплохо определиться для начала, о чём мы вообще спорим.
Pzz>По-моему, вы спорите о терминах
Скорее о трактовках и интерпретациях.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, 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. К тому же я не вижу смысла в этом коде.