Я понимаю основы перегрузки и переопределения, но что-то меня смущает. Я попытаюсь объяснить, используя простой пример:
У меня тогда есть следующий код:
void test(D& d1, B& b1, D& d2, B& b2){
d1.X(d2);
d1.X(b2);
b1.X(d2);
b1.X(b2);
}
int main(){
D d1, d2, d3, d4;
test(d1, d2, d3, d4);
}
Я очень не уверен, как третьи и четыре строки test()
будет определять, какие реализации X () вызывать и каковы общие механизмы, которые происходят.
Есть два шага: выбор перегрузки (X(B&)
против X(D&)
) и, как только это будет сделано, найти правильную реализацию выбранной функции. Первое происходит во время компиляции и зависит от статические типы объекта а также его аргументы, последний происходит во время выполнения и зависит от динамический тип объекта (обратите внимание, что это делает не зависит от динамического типа аргументов).
Четыре объекта объявлены следующим образом: d1
а также d2
являются D&
так их статический тип является D&
, а также b1
а также b2
объявлены как B&
поэтому их статический тип B&
, Статический тип — это то, что вы объявили в коде.
Но динамический тип для всех четырех D
, потому что все четыре ссылки на самом деле ссылаются на объекты, которые вы создали как D
-объекты в main()
,
Поэтому первый шаг, выбор перегрузки: в случае b1.X(b2)
а также b1.X(d2)
есть только одна возможная перегрузка, X(B&)
потому что статический тип B&
и определение класса для B
имеет только эту функцию. Но в случае d1.X(b2)
а также d1.X(d2)
выбор перегрузки основан на определении класса D
потому что статический тип D&
, Итак, рассматриваются две перегрузки: X(B&)
а также X(D&)
, Когда аргумент b2
, первая перегрузка выбрана, и когда аргумент d2
выбрана последняя перегрузка — все основано на статических (= объявленных) типах объектов и аргументов.
Второй шаг, выбор правильной реализации выбранной перегрузки. Это происходит во время выполнения и зависит от динамического типа объекта (не аргументов). Так что в случае b1.X(b2)
, потому что динамический тип b1
является D
, это в конечном итоге вызов D::X(B&)
, То же самое для b1.X(d2)
: Перегрузка, выбранная на предыдущем шаге, была X(B&)
, но выбранная реализация D::X(B&)
, (D::X(D&)
на данный момент не подходит, потому что это будет другая перегрузка, и перегрузка уже выбрана на основе статического типа). В случае d1.X(b2)
а также d1.X(d2)
, выбранные функции такие же, как на первом этапе, D::X(B&)
а также D::X(D&)
потому что динамический тип объекта совпадает со статическим типом.
Вы объявляете виртуальную функцию X
в B(B::X)
и переопределить X
в производном классе D(D::X)
, Если список параметров B::X
а также D::X
разные, B::X
а также D::X
считаются разными, D::X
не переопределяет B::X
, а также D::X
не является виртуальным (если вы не объявили его с помощью ключевого слова virtual). Вместо, D::X
шкуры B::X
,
#include <iostream>
using namespace std;
struct B {
virtual void X() { cout << "Class B" << endl; }
};
struct D: B {
void X(int) { cout << "Class D" << endl; }
};
int main() {
D d;
B* pb = &d;
// d.X();
pb->X();
}
Вы даже не можете позвонить d.X()
скрыт D::X(int)
, Но pb->X()
Это хорошо.
Итак, в вашем случае:
struct B {
virtual void X(B& b) { cout << "Class B" << endl; }
};
struct D: B {
void X(B& b) { cout << "Class D" << endl; }
void X(D& d) { cout << "Class D" << endl; }
};
D::X
скроет B::X
, Так d1.X(d2)
а также d1.X(b2)
в test()
не имеет ничего общего с B::X
, А также b1.X(d2)
, а также b1.X(b2)
в test()
позвоню D::X
, Хотя B::X
невидим в D, но D::X(B&)
является виртуальным, независимо от того, объявляете ли вы D::X(B&)
с виртуальным ключевым словом. Компилятор знает, что это виртуальная функция, поэтому D::X(B&)
вызывается.
РЕДАКТИРОВАТЬ: Дополнительные пояснения к b1.X (b2), B :: X — это виртуальная функция, и D :: X переопределяет ее, поэтому определенно она будет вызывать D :: X посредством динамического связывания. А перегрузка определяется во время компиляции, поэтому она не будет вызывать D :: X (D&).