Информация об изменениях

Сообщение Re: Вызов функции класса по ссылке от 01.12.2017 8:54

Изменено 01.12.2017 8:56 MasterZiv

Re: Вызов функции класса по ссылке
Здравствуйте, PavelCH, Вы писали:

PCH>struct ca

PCH>{
PCH> int a, b;
PCH> ca() : a(0), b(0)
PCH> {
PCH> }
PCH> void func()
PCH> {
PCH> a++;
PCH> }
PCH> typedef void(ca::*proc)();
PCH>};
PCH>struct cb : ca
PCH>{
PCH> void f2()
PCH> {
PCH> b++; c++;
PCH> }
PCH>};

PCH>} pdata[] = {

PCH> {"f2", (ca::proc)&cb::f2}, // Тут опасный код
PCH> {"func", &ca::func},
PCH>};
PCH>cb t1;
PCH>ca* p = &t1;
PCH>(p->*pdata[0].proc)(); // Есть ли какие-то подводные камни в таком вызове?
PCH>[/ccode]

Методы классов-наследников могут обращаться к данным классов-наследников (логично же),
а ты преобразуешь этот метод к указателю на (нестатический) метод класса-предка.
При вызове метода класса через указатель на метод класса ты обязан в вызове передать
ссылку на объект класса.
Указатель на метод класса-предка может быть вызван с любым объектом, являющимся
объектом класса-предка (ca в данном случае). Это значит, что это могут быть
объекты конечного класса ca, объекты конечного класс cb или любых наследников
(тут не определённых, но потенциально определяемых в будущем) от обоих этих классов,
т.е. как наследников ca, так и наследников cb (которые естественно являются наследниками и ca также).
При этом вызов методов класса cb для объектов разных конечных типов будет либо валидным, либо невалидным,
поскольку метод класса cb будет вызван с объектом, который НЕ является классом cb.
(нарушение принципа подстановки Лисков).
Для разных конечных классов объекта:
-- Класс ca -- вызов невалидный, поскольку реальный тип объекта — ca, а не cb
-- Класс cb -- вызов валидный, поскольку реальный тип объекта — cb
-- Класс -- какой-то наследник ca, но минуя в наследовании cb -- вызов невалидный, поскольку реальный тип объекта не cb
-- Класс -- какой-то наследник cb -- вызов валидный, поскольку реальный тип объекта cb

Соответственно, в части вариантов вызовов нарушается принцип подстановки и программа может быть невалидной.
Именно поэтому приведение указателя на метод или член класса-наследника к указателю на метод или член класса-предка
является неопределённым поведением (не проверял вот прямо сейчас в стандарте, но уверен, что именно так дело и обстоит).

Т.е. так писать программу нельзя.

Однако, если метод класса-наследника, который приведён к указателю на метод класса-предка, не обращается
к нестатическим данным класса-наследника
и не вызывает нестатические методы класса-наследника, которые в свою очередь
могут иметь доступ к нестатическим данным класса-наследника, или после преобразования к указателю на
метод класса-предка через этот указатель метод будет вызываться только с объектами, являющимися объектами
класса-наследника
, метод наследника может нормально работать.
Re: Вызов функции класса по ссылке
Здравствуйте, PavelCH, Вы писали:

PCH>struct ca

PCH>{
PCH> int a, b;
PCH> ca() : a(0), b(0)
PCH> {
PCH> }
PCH> void func()
PCH> {
PCH> a++;
PCH> }
PCH> typedef void(ca::*proc)();
PCH>};
PCH>struct cb : ca
PCH>{
PCH> void f2()
PCH> {
PCH> b++; c++;
PCH> }
PCH>};

PCH>} pdata[] = {

PCH> {"f2", (ca::proc)&cb::f2}, // Тут опасный код
PCH> {"func", &ca::func},
PCH>};
PCH>cb t1;
PCH>ca* p = &t1;
PCH>(p->*pdata[0].proc)(); // Есть ли какие-то подводные камни в таком вызове?
PCH>[/ccode]

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

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

Указатель на метод класса-предка может быть вызван с любым объектом, являющимся
объектом класса-предка (ca в данном случае). Это значит, что это могут быть
объекты конечного класса ca, объекты конечного класс cb или любых наследников
(тут не определённых, но потенциально определяемых в будущем) от обоих этих классов,
т.е. как наследников ca, так и наследников cb (которые естественно являются наследниками и ca также).

При этом вызов методов класса cb для объектов разных конечных типов будет либо валидным, либо невалидным,
поскольку метод класса cb будет вызван с объектом, который НЕ является классом cb.
(нарушение принципа подстановки Лисков).

Для разных конечных классов объекта:
-- Класс ca -- вызов невалидный, поскольку реальный тип объекта — ca, а не cb
-- Класс cb -- вызов валидный, поскольку реальный тип объекта — cb
-- Класс -- какой-то наследник ca, но минуя в наследовании cb -- вызов невалидный, поскольку реальный тип объекта не cb
-- Класс -- какой-то наследник cb -- вызов валидный, поскольку реальный тип объекта cb

Соответственно, в части вариантов вызовов нарушается принцип подстановки и программа может быть невалидной.
Именно поэтому приведение указателя на метод или член класса-наследника к указателю на метод или член класса-предка
является неопределённым поведением (не проверял вот прямо сейчас в стандарте, но уверен, что именно так дело и обстоит).

Т.е. так писать программу нельзя.

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