Порядок разрешения методов в C ++ - PullRequest
10 голосов
/ 22 июля 2010

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

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

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

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

Ответы [ 4 ]

23 голосов
/ 22 июля 2010

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


Алгоритм описан в стандарте C ++ & sect; [class.member.lookup] (10.2). По сути, он найдет ближайшую однозначную реализацию в графе суперкласса. Алгоритм работает так:

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

  2. Мы определяем поисковый набор S (f, C) , представляющий собой пару наборов ( & Delta; , & Sigma ; ) представляет все возможности. (& раздел; 10.2 / 3) * +1028 *

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

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

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

    Δ = {f in C};
    if (Δ != empty)
      Σ = {C};
    else
      Σ = empty;
    S(f, C) = (Δ, Σ);
    
  4. Если S (f, C) пусто (& sect; 10.2 / 5) ,

    • Вычислить S (f, B i ) , где B i - базовый класс C , для всех i .

    • Слияние каждого S (f, B i ) в S (f, C) по одному.

      if (S(f, C) == (empty, empty)) {
        B = base classes of C;
        for (Bi in B)
          S(f, C) = S(f, C) .Merge. S(f, Bi);
      }
      
  5. Наконец, набор объявлений возвращается в результате разрешения имени (& sect; 10.2 / 7) .

    return S(f, C).Δ;
    
  6. Слияние двух справочных наборов ( & Delta; 1 , & Sigma; 1 ) и ( & Delta; 2 , & Sigma; 2 ) определяется как (& sect; 10.2 / 6) :

    • Если каждый класс в Σ 1 является базовым классом хотя бы одного класса в Σ 2 , вернуть ( & Delta; 2 , Σ 2 ).
      (Аналогично для обратного.)
    • Иначе, если Δ 1 Δ 2 , возврат ( неоднозначный , Σ 1 & cup; Σ 2 ).
    • В противном случае вернуть ( Δ 1 , Σ 1 & cup; Σ 2 )

      function Merge ( (Δ1, Σ1), (Δ2, Σ2) ) {
      
         function IsBaseOf(Σp, Σq) {
           for (B1 in Σp) {
             if (not any(B1 is base of C for (C in Σq)))
               return false;
           }
           return true;
         }
      
         if      (Σ1 .IsBaseOf. Σ2) return (Δ2, Σ2);
         else if (Σ2 .IsBaseOf. Σ1) return (Δ1, Σ1);
         else {
            Σ = Σ1 union Σ2;
            if (Δ1 != Δ2)
              Δ = ambiguous; 
            else
              Δ = Δ1;
            return (Δ, Σ);
         }
      }
      

Например (& sect; 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.
2 голосов
/ 22 июля 2010

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

vtable и vptr

vtable

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

0 голосов
/ 22 июля 2010

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

0 голосов
/ 22 июля 2010

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

...