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

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

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.

Как это случилось?

 Martin York06 июл. 2009 г., 21:23
Потому что компилятор делает это возможным! Если вызов обычного метода ничем не отличается от вызова виртуального метода, почему вы думаете, что код отличается от использования указателей на методы. Как вы думаете, во что компилятор переводит обычные (виртуальные и обычные) вызовы методов?

Ответы на вопрос(3)

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

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

 07 июл. 2009 г., 12:44
Компилятору определенно разрешено помещать все методы, виртуальные или нет, в vtable. Если это так, то он может использовать индекс vtable для указателей на функции-члены. На самом деле это довольно просто для компилятора - просто убедитесь, что не виртуальный переопределитель получает собственную запись vtable вместо перезаписи записи базового класса.
 06 июл. 2009 г., 17:52
Это могло бы иметь место, если бы существовало разделение между указателями на обычные функции-члены и указателями на виртуальные функции-члены. Однако, как я уже упоминал, это не так, и это все, о чем идет речь.
Решение Вопроса

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

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

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

 06 июл. 2009 г., 17:42
привет, ты только что указал на thunk. Есть хорошая статья, объясняющая thunk?
 06 июл. 2009 г., 17:58
«слово thunk относится к фрагменту низкоуровневого кода, обычно сгенерированного машиной, который реализует некоторые детали конкретной программной системы».en.wikipedia.org/wiki/Thunk, На этой странице обсуждаются несколько видов громов, но не этот конкретный.

потому что в стандарте говорится, как это должно происходить. Я провел несколько тестов с 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 C++ ABI, который указывает, что.

 06 июл. 2009 г., 17:59
Спасибо, это звучит логично. Тем не менее, несколько неэффективно для реализации C ++ ... Проверено код на VC, и результаты совершенно разные. Выводом является «c01380, c01390», который выглядит как адрес чего-либо.
 06 июл. 2009 г., 18:07
Оформить заявку на статью быстрого делегата, на которую ссылается @onebyone. Содержит приятную информацию о других компиляторах. Мой ответ, конечно, специфичен для GCC.
 06 июл. 2009 г., 18:33
Стандарт говорит, что это должно работать. Так что компилятор, соответствующий стандартам, будет работать. И на других реализациях, кроме gcc, указатели на виртуальные функции могут быть 16 байтов.
 06 июл. 2009 г., 17:45
Я полагаю, что ответ на мой вопрос не стандартизирован в C ++. Однако, как и vtables, я не знаю ни одного компилятора, который бы не использовал vtables в качестве механизма для виртуальных функций, поэтому я полагаю, чтоstandard механизм для этого также. Ваш ответ только делает меня более запутанным. Если компилятор хранит 1 и 5 в указателях на функции-члены A, как он может определить, является ли это индексом vtable или реальным адресом? (обратите внимание, что нет разницы между указателями на обычные и виртуальные функции-члены)
 06 июл. 2009 г., 17:58
Почему этот ответ делает вас более запутанным? Просто спросите, есть ли ничего неясного. Такие вещи не стандартизированы. Все зависит от реализации, чтобы придумать способы ее решения. Они могут решить, является ли это указателем на функцию или нет: я думаю, именно поэтому они добавляют 1. Поэтому, если число не выровнено, это смещение vtable. Если он выровнен, это указатель на функцию-член. Это только мое предположение, хотя.

Ваш ответ на вопрос