Подтвердить что ты не робот

Указатели на виртуальные функции-члены. Как это работает?

Рассмотрим следующий код С++:

class A
{
public:
      virtual void f()=0;
};


int main()
{
     void (A::*f)()=&A::f;
}

Если бы мне пришлось догадаться, я бы сказал, что & A:: f в этом контексте будет означать "адрес реализации A f()", так как нет явного разделения между указателями на обычный член функций и виртуальных функций-членов. А поскольку A не реализует f(), это будет ошибкой компиляции. Однако это не так.

И не только это. Следующий код:

void (A::*f)()=&A::f;
A *a=new B;            // B is a subclass of A, which implements f()
(a->*f)();

на самом деле вызовет B:: f.

Как это происходит?

4b9b3361

Ответ 1

Здесь слишком много информации о указателях функции-члена. Там некоторые вещи о виртуальных функциях в разделе "Хорошо продуманные компиляторы", хотя IIRC, когда я читал статью, я сглаживал эту часть, так как статья на самом деле о реализации делегатов на С++.

http://www.codeproject.com/KB/cpp/FastDelegate.aspx

Короткий ответ заключается в том, что он зависит от компилятора, но одна из возможностей заключается в том, что указатель на функцию-член реализуется как структура, содержащая указатель на функцию "thunk", которая делает виртуальный вызов.

Ответ 2

Это работает, потому что Стандарт говорит, что это должно произойти. Я провел несколько тестов с GCC, и, оказывается, для виртуальных функций GCC хранит смещение виртуальной таблицы рассматриваемой функции в байтах.

struct A { virtual void f() { } virtual void g() { } }; 
int main() { 
  union insp { 
    void (A::*pf)();
    ptrdiff_t pd[2]; 
  }; 
  insp p[] = { { &A::f }, { &A::g } }; 
  std::cout << p[0].pd[0] << " "
            << p[1].pd[0] << std::endl;
}

Эта программа выводит 1 5 - байтовые смещения элементов виртуальной таблицы этих двух функций. Он следует за Itanium С++ ABI, который указывает, что.

Ответ 3

Я не совсем уверен, но я думаю, что это просто регулярное полиморфное поведение. Я думаю, что &A::f на самом деле означает адрес указателя функции в классе vtable и почему вы не получаете ошибку компилятора. Пространство в таблице vtable по-прежнему выделено, и это место, в которое вы фактически возвращаетесь.

Это имеет смысл, потому что производные классы существенно переписывают эти значения указателями на их функции. Вот почему (a->*f)() работает во втором примере - f ссылается на vtable, которая реализована в производном классе.