Разрешение метода заказывает в C++

Рассмотрите следующую иерархию классов:

  • Объект базового класса с виртуальным нечто метода ()
  • произвольная иерархия со множественным наследованием (виртуальный и невиртуальный); каждый класс является подтипом Объекта; некоторые из них переопределяют нечто (), некоторые не делают
  • класс X от этой иерархии, не переопределяющее нечто ()

Как определить, какой метод будет выполняться на вызов нечто () на объекте класса X в C++?

(Я ищу алгоритм, не любой конкретный случай.)

10
задан Kos 22 July 2010 в 16:08
поделиться

4 ответа

В C ++ нет MRO, как в Python. Если метод неоднозначен, это ошибка времени компиляции. Независимо от того, является ли метод виртуальным или нет, это не влияет на него, но влияет виртуальное наследование .


Алгоритм описан в стандарте C ++ § [class.member.lookup] (10.2). В основном он найдет наиболее близкую однозначную реализацию в графе суперкласса. Алгоритм работает следующим образом:

  1. Предположим, вы хотите найти функцию f в классе C .

  2. Мы определяем поисковый набор S (f, C) , представляющий собой пару наборов ( Δ , Σ ), представляющих все возможности . (§10.2 / 3)

    • Набор Δ называется набором объявлений , который, по сути, представляет собой все возможные f .

    • Набор Σ называется набором подобъектов , который содержит классы, в которых находятся эти f .

  3. Пусть S (f, C) включает все f , непосредственно определенные (или с использованием -ed) в C , если есть (§10.2 / 4) :

     Δ = {f в C};
    если (Δ! = пусто)
    Σ = {C};
    еще
    Σ = пусто;
    S (f, C) = (Δ, Σ);
    
  4. Если 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);
      }
      
  5. Наконец, в результате разрешения имен возвращается набор объявлений (§10.2 / 7) .

     вернуть S (f, C) .Δ;
    
  6. Слияние двух поисковых наборов ( Δ 1 , Σ 1 ) и ( Δ 2 , Σ 2 ) определяется как (§10.2 / 6) :

    • Если каждый класс в Σ 1 является базовым классом хотя бы одного класса в Σ 2 , верните ( Δ 2 , Σ 2 ).
      (Аналогично для обратного.)
    • Иначе, если Δ 1 Δ 2 , вернуть ( неоднозначное , Σ 1 Σ 2 ).
    • В противном случае верните ( Δ 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.
24
ответ дан 3 December 2019 в 16:08
поделиться

Некоторое подробное описание с кодом.

vtable и vptr

vtable

Виртуальные функции

2
ответ дан 3 December 2019 в 16:08
поделиться

Если вы говорите о G ++ , используется vtable ( Таблица виртуальных методов ), вы можете получить более подробную информацию здесь . Не уверен, что все компиляторы C ++ используют один и тот же подход, но я бы сказал, что да

0
ответ дан 3 December 2019 в 16:08
поделиться

Если метод базового класса является виртуальным, каждый вызов его через базовый или производный указатель / ссылку будет вызывать соответствующий метод (самый дальний по дереву наследования). Если метод был объявлен виртуальным, у вас не может быть другого пути позже: объявление его виртуальным (или нет) в производных классах ничего не изменит.

0
ответ дан 3 December 2019 в 16:08
поделиться
Другие вопросы по тегам:

Похожие вопросы: