singalen: (2002)
[personal profile] singalen
Продолжаем набивать журнал контентом.
Что будет, если указателю на функцию-член присвоить указатель на виртуальный метод предка, а потом вызвать её на экземпляре потомка?
А для невиртуальной функции?
#include <iostream>

class A
{
public:
  virtual void f() { std::cout << "A::f()\n"; };
  void g() { std::cout << "A::g()\n"; };
};

class B : public A
{
public:
  virtual void f() { std::cout << "B::f()\n"; };
  void g() { std::cout << "B::g()\n"; };
};

typedef void (A::*VirtualFunctionPointer) ();

int main()
{
  A a;
  B b;
  B* pb = &b;

  VirtualFunctionPointer p = &A::f;

  (pb->*p) ();

  p = &A::g;
  (pb->*p) ();

  return 0;
}

И на gcc, и на MSVC выдаёт одно и то же:
B::f()
A::g()

Вывод интересен: указатель на функцию-член хранит И индекс в VMT, И указатель на функцию. Ну и флаг, указывающий, что именно хранится.

(no subject)

31/1/06 03:26 (UTC)
Posted by [identity profile] cossac.livejournal.com
юзерпик жжот :)

fun

1/2/06 06:37 (UTC)
Posted by (Anonymous)
Интерестный пример. Но на мой взгляд польза примера в том, что после операции p=&A::f; С++ не хранит адрес этих функций, а точнее стандартом не прописаны значения указателя на функцию. Поэтому то, что хранится в p, после каждого присваивания не известно(например, это индекс в виртуальной таблице).

Ко всему, еще есть одна особенность, насколько я знаю, указатель на функцию-член имеет жесткую привязку к типу(классу) в котором эта функция описана, но в этом примере в первом случае вызов (pb->*p) (); эквивалентен B::f()(так как B наследник класса A), т.е. можно сказать, что сработал механизм преобразования имени виртуальной функции в индекс в виртуальной таблице. Для второго присваивания все более проще, при присваивании p = &A::g, а сам p является просто "указателем" на функции-члены класса A и только A, то поэтому и произошел вызов функции A::g(), причем его скорее всего можно интерпритировать как pb->A::g();

P.S. Все вышенаписанное сугубо мое ИМХО :-)

(no subject)

1/2/06 06:52 (UTC)
Posted by (Anonymous)
Вообще-то, это всего лишь мои размышления на тему, почему так работает приведенный пример. Просто пришлось напрячься, чтобы разобраться. Очень понравилось, поскольку когда я разбираю такие примеры я улучшаю знание языка...

(no subject)

1/2/06 07:20 (UTC)
Posted by (Anonymous)
Кстати, вроде А. Александреску рассматривает похожие "фишки" в "Modern C++ Design", точно не помню...

(no subject)

1/2/06 07:52 (UTC)
Posted by (Anonymous)
Кстати, в разделе 15.5(Страуструп) поясняется "в отличие от указателя на переменную или обычную функцию, указатель на член не является просто указателем на область памяти. Он больше соответствует смещению в структуре или индексу в массиве."+(и действительно вывод этого указателя - целое число)+"Сочетание указателя на член с указателем на соответствующий объект дает то, что идентифицирует конкретный член конкретного объекта". Туманно, но для меня все прояснилось до конца...

P.S. наверно я вас достал своими постами, звыняйте... :-)

(no subject)

3/2/09 12:31 (UTC)
Posted by [identity profile] klizardin.livejournal.com
да, интересно.

(no subject)

24/4/10 07:29 (UTC)
Posted by (Anonymous)
ну собственно как это на asm выглядит (чтд):

//VirtualFunctionPointer p = &A::f; //BEGIN

:004012AA 8B05A4A04000 mov eax, dword ptr [0040A0A4]

//mov [p],eax //И
:004012B0 890578C74000 mov dword ptr [0040C778], eax
:004012B6 8B05A8A04000 mov eax, dword ptr [0040A0A8]

//mov [p+0x4],eax //И
:004012BC 89057CC74000 mov dword ptr [0040C77C], eax
:004012C2 8B05ACA04000 mov eax, dword ptr [0040A0AC]

//mov [p+0x8],eax //И
:004012C8 890580C74000 mov dword ptr [0040C780], eax

//VirtualFunctionPointer p = &A::f; //END



//(pb->*p) ();
:004012CE FF75D0 push [ebp-30]
:004012D1 FF1578C74000 call dword ptr [0040C778]
:004012D7 59 pop ecx

//....

//(pb->*p) ();
:004012D8 FF75CC push [ebp-34]
:004012DB FF1578C74000 call dword ptr [0040C778]
:004012E1 59 pop ecx