Рассмотрите следующую иерархию классов:
Как определить, какой метод будет выполняться на вызов нечто () на объекте класса X в C++?
(Я ищу алгоритм, не любой конкретный случай.)
В C ++ нет MRO, как в Python. Если метод неоднозначен, это ошибка времени компиляции. Независимо от того, является ли метод виртуальным или нет, это не влияет на него, но влияет виртуальное наследование .
Алгоритм описан в стандарте C ++ § [class.member.lookup] (10.2). В основном он найдет наиболее близкую однозначную реализацию в графе суперкласса. Алгоритм работает следующим образом:
Предположим, вы хотите найти функцию f в классе C .
Мы определяем поисковый набор S (f, C) , представляющий собой пару наборов ( Δ , Σ ), представляющих все возможности . (§10.2 / 3)
Набор Δ называется набором объявлений , который, по сути, представляет собой все возможные f .
Набор Σ называется набором подобъектов , который содержит классы, в которых находятся эти f .
Пусть S (f, C) включает все f , непосредственно определенные (или с использованием
-ed) в C , если есть (§10.2 / 4) :
Δ = {f в C};
если (Δ! = пусто)
Σ = {C};
еще
Σ = пусто;
S (f, C) = (Δ, Σ);
Если S (f, C) пусто (§10.2 / 5) ,
Вычислить S (f, B i ) , где B i - базовый класс C для всех i .
Объедините каждый S (f, B i ) в S (f, C) по одному.
if (S (f, C) == (пусто, пусто)) {
B = базовые классы C;
для (Bi в B)
S (f, C) = S (f, C) .Слияние. S (f, Bi);
}
Наконец, в результате разрешения имен возвращается набор объявлений (§10.2 / 7) .
вернуть S (f, C) .Δ;
Слияние двух поисковых наборов ( Δ 1 , Σ 1 ) и ( Δ 2 , Σ 2 ) определяется как (§10.2 / 6) :
В противном случае верните ( Δ 1 , Σ 1 ∪ Σ 2 )
function Merge (( Δ1, Σ1), (Δ2, Σ2)) {
функция IsBaseOf (Σp, Σq) {
для (B1 в Σp) {
if (не любой (B1 - база C для (C в Σq)))
вернуть ложь;
}
вернуть истину;
}
если (Σ1 .IsBaseOf. Σ2) return (Δ2, Σ2);
иначе, если (Σ2 .IsBaseOf. Σ1) return (Δ1, Σ1);
еще {
Σ = Σ1 объединение Σ2;
если (Δ1! = Δ2)
Δ = неоднозначно;
еще
Δ = Δ1;
return (Δ, Σ);
}
}
Например (§10.2 / 10) ,
struct V { int f(); };
struct W { int g(); };
struct B : W, virtual V { int f(); int g(); };
struct C : W, virtual V { };
struct D : B, C {
void glorp () {
f();
g();
}
};
Мы вычисляем, что
S(f, D) = S(f, B from D) .Merge. S(f, C from D)
= ({B::f}, {B from D}) .Merge. S(f, W from C from D) .Merge. S(f, V)
= ({B::f}, {B from D}) .Merge. empty .Merge. ({V::f}, {V})
= ({B::f}, {B from D}) // fine, V is a base class of B.
и
S(g, D) = S(g, B from D) .Merge. S(g, C from D)
= ({B::g}, {B from D}) .Merge. S(g, W from C from D) .Merge. S(g, V)
= ({B::g}, {B from D}) .Merge. ({W::g}, {W from C from D}) .Merge. empty
= (ambiguous, {B from D, W from C from D}) // the W from C is unrelated to B.
Если вы говорите о G ++
, используется vtable ( Таблица виртуальных методов ), вы можете получить более подробную информацию здесь . Не уверен, что все компиляторы C ++ используют один и тот же подход, но я бы сказал, что да
Если метод базового класса является виртуальным, каждый вызов его через базовый или производный указатель / ссылку будет вызывать соответствующий метод (самый дальний по дереву наследования). Если метод был объявлен виртуальным, у вас не может быть другого пути позже: объявление его виртуальным (или нет) в производных классах ничего не изменит.