2009-06-09 57 views
5

我有时候读过(可能在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:我删除了问题的第二部分“它是如何工作的?”。看起来这就是引人注目的。

EDIT-2:我搜索了c.l.C++。moderated,碰到了这个linkhttp://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/5ddde8cf1ae59a0d)。共识似乎是因为标准不限制它,它是vaild。

编辑-3:在阅读codeproject文章(感谢ovanes)之后,我在想编译器会有一些神奇的功能。由于虚函数是通过vtable(编译器特定的)实现的,因此获取虚函数的地址总是会在vtable中给出偏移量。根据所使用的'this'指针,调用相应的函数(其地址在偏移量处)。我不知道该如何去证明这一点,因为标准没有说出任何东西!

回答

0

我想这是未定义的。我在规范中找不到任何东西。

使用名为vtable的概念实现虚拟方法。

我会说它是编译器实现特定的。我真的不认为它是一个纯粹的虚拟,如果它只是虚拟的,也会发生。

我刚刚编译你的代码与Visual Studio 2008和拆解EXE。 VS2008所做的是创建一个thunk函数,使用传入的'this'指针跳转到vtable条目。

这是对callVirtual模板函数的设置和调用。

push offset [email protected]@$B3AE ; void (__thiscall *)(Base *) 
lea  eax, [ebp+ptr] 
push eax    ; Base ** 
call [email protected]@@[email protected]@@[email protected]@[email protected]@Z ; callVirtual<Base *,void (Base::*)(void)>(Base * &,void (Base::*)(void)) 

因此,传递函数指针thunk函数:[email protected]@$B3AE

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

所有thunk函数是使用V表跳转到真正的类方法做。

+0

感谢。你知道C++标准的一部分,它提到了指向纯虚拟的成员函数指针。我试图找到,但无法找到任何具体的细节。在我看来,由于允许指向不完整类型的成员指针,因此也允许指向纯虚拟的成员指针。 – Abhay 2009-06-09 07:35:43

+0

我错读了这个问题。我会更新我的答案。 – 2009-06-09 21:43:44

+0

+1您编辑的答案接近这个问题的答案:-)。我开始认为考虑虚拟函数(纯粹与否)的地址并调用它更接近于实现定义的语言方面。 – Abhay 2009-06-10 06:01:58

1

当然可以。您的验证码已不仅仅是通话这样的纯虚方法不同:

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

唯一的区别是,你有另一种机制做呼叫,在这种情况下,通过callVirtual()。

1

正如Magnus Skog所说,模板部分并不真正相关。什么它归结为是:

(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

在我看来,不一致的地址。这是我背后的一些理由吗?

1

以下文章广泛讨论了C++中的成员函数指针,它们如何实现以及缺陷在哪里。它还处理虚拟成员函数指针等等。我认为它会回答你所有的问题。

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

它也展示了如何实现代表在C++中,哪些陷阱你可能陷阱。

随着亲切的问候,

Ovanes