Каковы правила динамической диспетчеризации в С++?
Интересно, как динамическая диспетчеризация действительно работает на С++. Чтобы проиллюстрировать мой вопрос, я начну с кода Java.
class A
{
public void op(int x, double y) { System.out.println("a"); }
public void op(double x, double y) { System.out.println("b"); }
}
class B extends A
{
public void op(int x, double y) { System.out.println("c"); }
public void op(int x, int y) { System.out.println("d"); }
}
class C extends B
{
public void op(int x, int y) { System.out.println("e"); }
}
public class Pol
{
public static void main(String[] args)
{
A a = new C();
B b = new C();
/* 1 */ a.op(2, 4);
/* 2 */ b.op(2.0, 4.0);
}
}
Вызов a.op(2, 4)
будет печатать "c" , так как действительно компилятор:
- смотрит в класс
A
(так как A
объявляется переменной типа A
), какой метод близок к op(int, int)
,
- не может найти метод
op(int, int)
, но находит метод op(int, double)
(с одним авто-литьем int
→ double
),
- затем фиксирует эту подпись.
Во время выполнения JVM:
- ищет метод с сигнатурой
op(int, double)
, установленный компилятором в класс C
, но не находит его,
- просматривает C суперкласс, т.е.
B
,
- и, наконец, находит метод
op(int, double)
, затем вызывает его.
Тот же принцип применяется к вызову b.op(2.0, 4.0)
, который печатает "b".
Теперь рассмотрим эквивалентный код в С++
#include <iostream>
class A
{
public:
virtual void op(int x, double y) { std::cout << "a" << std::endl; }
virtual void op(double x, double y) { std::cout << "b" << std::endl; }
};
class B : public A
{
public:
void op(int x, double y) { std::cout << "c" << std::endl; }
virtual void op(int x, int y) { std::cout << "d" << std::endl; }
};
class C : public B
{
public:
void op(int x, int y) { std::cout << "e" << std::endl; }
};
int main()
{
A *a = new C;
B *b = new C;
/* 1 */ a->op(2, 4);
/* 2 */ b->op(2.0, 4.0);
delete a;
delete b;
}
a->op(2, 4)
будет печатать "c" , как Java. Но b->op(2.0, 4.0)
выводит "c" снова, и там, я потерян.
Каковы правила, применяемые при компиляции и во время выполнения на С++ для динамической диспетчеризации?
(Обратите внимание, что у вас будет такое же поведение с кодом С++, если вы пишете virtual
перед каждой функцией, здесь ничего не меняется)
Ответы
Ответ 1
Для С++, когда вы выполняете b->op(2.0, 4.0);
, компилятор просматривает B
, находит метод, который он может вызвать (int x, double y)
, и использует его. Он не выглядит в суперклассе, если какой-либо метод в подклассе может обрабатывать вызов. Это называется методом скрытия, т.е. op(double, double)
скрыт.
Если вы хотите, чтобы он выбрал версию (double x, double y)
, вам нужно сделать функцию видимой внутри B
со следующим объявлением внутри B
:
using A::op;
Дальнейшее объяснение правил
Ответ 2
Объявив новую перегрузку op
в B
, вы скрыли базовые версии. Компилятор будет отправлять только на основе "B", поэтому он выбирает op(int,double)
.
Ответ 3
Компилятор будет предупреждать/совершать ошибки при конверсиях, если вы сообщите об этом. Используя gcc, аргументы компилятора -Wconversion -Werror
не позволяют компилировать ваш код, поскольку вы правы, здесь существует потенциальная потеря точности.
Учитывая, что вы не включили этот параметр компилятора, компилятор рад разрешить ваш вызов b- > op (double, double) в B:: op (int, double).
Пожалуйста, имейте в виду, что это решение времени компиляции, а не решение времени исполнения/полиморфности.
Фактическая vtable указателя "b" будет иметь метод op (int, int), доступный во время выполнения, но компилятор не знает об этом методе во время компиляции. Он может только предположить, что указатель b имеет тип B *.
Ответ 4
Вы начинаете с полиморфного поведения в базовом классе A
. Затем, используя ту же подпись, вы не можете остановить это в производных классах.
Это не обязательно, если вы объявите тот же метод virtual
или нет.
Вы должны изменить подпись!
Кроме того, у вас есть проблема видимости. Эта строка
B *b = new C;
b->op(2.0, 4.0);
Компилятор ищет метод в вашем классе B
. op
-методы скрывают методы с тем же именем класса A
(разрешение перегрузки). Если он найдет что-то полезное, он просто использует его.