Виртуальные звонки с использованием адреса чистого виртуального участника. Это законно? - PullRequest
5 голосов
/ 09 июня 2009

Некоторое время назад я читал (вероятно, на c.l.c ++. Moderated), что вызовы виртуальных функций могут быть шаблонизированы. Я пробовал что-то в следующих строках.

#include <iostream>

template<class T, class FUN> 
void callVirtual(T& t, FUN f){ 
   (*t.*f)(); 
} 


struct Base{ 
   virtual ~Base(){} 
   virtual void sayHi()=0; 
}; 


struct Derived : public Base{ 
   void sayHi(){ 
      std::cout << "Hi!" << std::endl; 
   } 
}; 


void Test(){ 
   Base* ptr = new Derived; 
   callVirtual(ptr,&Base::sayHi); 
} 

int main()
{
   Test();
   return 0;
}

Output:
Hi!

Шаблонный метод, хотя и получает адрес метода чисто виртуального базового члена во время компиляции, вызывает правильный метод во время выполнения. Законно ли в стандарте C ++ принимать адрес чисто виртуального члена?

Заранее спасибо

РЕДАКТИРОВАТЬ-1: Я удалил вторую часть вопроса «как это работает?». Похоже, это то, что привлекает внимание.

РЕДАКТИРОВАТЬ-2: Я искал clc ++. Модерируемый и наткнулся на эту ссылку (http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/5ddde8cf1ae59a0d). Консенсус выглядит так, как стандарт не ограничивает его Вальд.

РЕДАКТИРОВАТЬ-3: После прочтения статьи codeproject (спасибо ovanes) я думаю, что компиляторы совершают магию. Поскольку виртуальные функции реализуются через vtable (который зависит от компилятора), получение адреса виртуальной функции всегда дает смещение в vtable. В зависимости от используемого указателя this вызывается соответствующая функция (адрес которой по смещению). Я не уверен, как подтвердить это, хотя в стандарте об этом ничего не сказано!

Ответы [ 4 ]

1 голос
/ 09 июня 2009

В следующей статье подробно обсуждаются указатели на функции-члены в C ++, как они реализованы и в чем недостатки. Он также обрабатывает виртуальные указатели на функции-члены и многое другое. Я думаю, что он ответит на все ваши вопросы.

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

В нем также показано, как реализовать делегаты в C ++ и какие подводные камни вы можете поймать.

С уважением,

Ованес

1 голос
/ 09 июня 2009

Конечно, это так. Ваш код ничем не отличается от простого вызова чисто виртуального метода, подобного этому:

void Test()
{ 
   Base* ptr = new Derived; 
   ptr->sayHi();
   delete ptr;
} 

Единственное отличие состоит в том, что у вас есть другой механизм для выполнения вызова, и в этом случае через callVirtual ().

1 голос
/ 09 июня 2009

Как сказал Магнус Ског, часть шаблона на самом деле не актуальна. То, что это сводится к тому, что:

(ptr->* &Base::sayHi)()

вроде бы работает, но

ptr->Base::sayHi()

, очевидно, не потому, что sayHi чисто виртуальный.

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

Редактировать

Даже если оно действительно, как говорят ваши правки, мне все еще интересно, что это значит.

Если для простоты предположить, что sayHi не является чистым (поэтому существует определение Base::sayHi), то что произойдет, если я возьму его адрес? Получу ли я адрес Base :: sayHi или адрес функции, на которую указывает vtable (в данном случае Derived :: sayHi)?

Видимо, компиляторы предполагают последнее, но почему? Вызов ptr->Base::sayHi() вызывает sayHi в базовом классе, но взятие адреса Base::sayHi дает мне адрес Derived::sayHi

Мне это кажется противоречивым. Есть ли какое-то объяснение этому, которого я пропускаю?

0 голосов
/ 09 июня 2009

Я бы предположил, что это не определено. Я не смог найти ничего в спецификации.

Виртуальные методы

реализованы с использованием концепции, называемой vtable .

Я бы сказал, что это зависит от реализации компилятора. Я не думаю, что имеет значение, что это чисто виртуальный, то же самое произошло бы, если бы это было просто виртуально

Я только что скомпилировал ваш код в Visual Studio 2008 и разобрал исполняемый файл. VS2008 создает функцию thunk для перехода к записи vtable, используя переданный указатель this.

Это настройка и вызов функции шаблона callVirtual.

push    offset j_??_9Base@@$B3AE ; void (__thiscall *)(Base *)
lea     eax, [ebp+ptr]
push    eax             ; Base **
call    j_??$callVirtual@PAUBase@@P81@AEXXZ@@YAXAAPAUBase@@P80@AEXXZ@Z ; callVirtual<Base *,void (Base::*)(void)>(Base * &,void (Base::*)(void))

То есть, передавая указатель на функцию thunk: j_??_9Base@@$B3AE

; void __thiscall Base___vcall_(Base *)
j_??_9Base@@$B3AE proc near
jmp     ??_9Base@@$B3AE ; [thunk]: Base::`vcall'{4,{flat}}
j_??_9Base@@$B3AE endp

Все, что выполняет функция thunk, - это использование vtable для перехода к методу реального класса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...