Здравствуйте, 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, ...
Ну да. Позовётся виртуальная функция, соответствующая тому уровню создания объекта, к которому относится конструктор. А вот когда объект создастся до конца (т.е., не в конструкторе), то та же виртуальная функция будет соответствовать самому "верхнему" производному классу, который сподобился ее переопределить.
Стандарт и книжка говорят об одном.
Надеюсь хоть, деструктор ведет себя симметрично. Хотя кто его знает, чего ждать от этого плюсплюса.
Здравствуйте, LaptevVV, Вы писали:
ЛБ>>А вы мне советуете выбросить на ветер 1140 рублей. Нехорошо. LVV>Ну, ничего такого я не советовал. LVV>Я рекомендовал книжку почитать. LVV>Вот вы почитали, и пост родился. Другие заинтересуются...
Причём забесплатно, небось, почитал. А ты ему, выходит, теперь 1140 рублей должен.
Здравствуйте, 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 в параграфе часть которого я процитировал выше.
Здравствуйте, Лазар Бешкенадзе, Вы писали:
ЛБ>Вот ты говоришь здесь есть виртуальная диспетчеризация а здесь ее нет а я не видел таких различий в стандарте, ты "выше даешь определение виртуальности," а я не видел такого определения в стандарте.
А книжка не текст стандарта обсуждает.
ЛБ>В стандарте делается различие между виртуальным вызовом и статическим, а твоих определений там нет.
И я не текст стандарта обсуждаю.
ЛБ>И под виртуальной диспетчеризацией я понимаю
Мне фиолетово что вы под виртуальной диспетчеризацией понимаете, т.к. речь идет не о вас, а о том, что написано в обсуждаемой книге. А там написано о сюрпризах, которые могут поджидать людей, по незнанию предполагающих, что в C++ вызов виртуального метода в конструкторе/деструкторе будет вести себя так же, как вызов оного метода в любом другом месте, и что в C++ будет совсем другое поведение, нежели во многих других языках с поддержкой ООП -- Java, C#, Ruby, Python). О чем автор буквально и пишет:
В конструкторах и деструкторах в C++ виртуальная диспетчеризация методов не работает (В других языках — например, в C# или Java — наоборот, что доставляет свои проблемы).
PS. Мне, блин, сложно понять почему кто-то доколупался к этому разделу книги. Я сам в свое время наступил на те же грабли. И с другими языками поработал, в которых поведение виртуальных методов в конструкторе отличное от C++ного. Поэтому то, что написано в этом разделе воспринимается просто, понятно и достоверно. А желающие доколупаться до букв идут лесом.
Здравствуйте, Marty, Вы писали:
S>>То, что это к виртуальной диспетчеризации нормального человека не имеет отношения -- это уже другой вопрос. Но ведь работает же
M>Это тоже виртуальная диспетчеризация
Ну, хорошо. Виртуальная диспетчеризация работает, но её результат неотличим от прямого невиртуального вызова. Нет возражений против такой формулировки?
Так а стоп. А почему тогда мы уверены, что она работает? В чём это проявляется и как это можно проверить?
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
S>>>То, что это к виртуальной диспетчеризации нормального человека не имеет отношения -- это уже другой вопрос. Но ведь работает же
M>>Это тоже виртуальная диспетчеризация
R>Ну, хорошо. Виртуальная диспетчеризация работает, но её результат неотличим от прямого невиртуального вызова. Нет возражений против такой формулировки?
R>Так а стоп. А почему тогда мы уверены, что она работает? В чём это проявляется и как это можно проверить?
Пришел к выводу, что причина спора в этом обсуждении в том, что часть участников (Лазар, Marty, watchmaker) придерживаются той точки зрения, что механизм виртуальной диспетчеризации работает потому, что он работает именно так, как описано в стандарте. Т.е. если в программе происходит именно то, что и следует по стандарту языка C++, то значит все работает и работает так, как надо.
Тогда как автор книги и ваш покорный слуга придерживаемся той точки зрения, что есть неопытный пользователь языка C++, который допускает неожиданную для себя ошибку из-за своих ожиданий о том, как работает виртуальный вызов с точки зрения пользователя (подкрепленной, возможно, опытом из других языков программирования).
Т.е. есть как бы "виртуальный вызов с точки зрения общепринятых ожиданий" и есть "виртуальный вызов с точки зрения языка C++".
Поэтому одна группа (в которой я) говорит о том, что в конструкторе виртуальности нет.
А другая группа, говорит о том, что она есть (опираясь на стандарт C++).
Суть же в том, что проблемная глава ориентирована не на знатоков стандарта.
Здравствуйте, 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. компилятор не делал такой оптимизации — виртуальный вызов медленнее статического и можно измерить разницу в скорости