5

我需要使用一个成员函数指针,它接受在其他代码中使用的基类的参数。好吧,我只是想做一些类似下面例子的事情。这段代码工作正常,但我想知道这样的演员是否总是安全的?我不能在这里做dynamicstatic投。

#include <cstdio>                                                   

class C
{                                                           
public:                                                             
        C () : c('c') {}                                            
        virtual ~C() {}                                             

        const char c;                                               
};                                                                  

class D : public C
{                                                
public:                                                             
        D () : d('d') {}                                            
        virtual ~D() {}                                             

        const char d;                                               
};                                                                  

class A 
{                                                           
public:                                                             
        A () {}                                                     
        virtual ~A() {}                                             

        void f( C& c ) { printf("%c\n",c.c); }                      
        void g( D& d ) { printf("%c %c\n",d.c,d.d); }               
};                                                                  

int main (int argc, char const* argv[])                             
{                                                                   
        void (A::*pf)( C& c ) = &A::f;                              
        void (A::*pg)( D& d ) = reinterpret_cast<void (A::*)(D&)>(&A::f);

        A a;                                                        
        C c;                                                        
        D d;                                                        

        (a.*pf)( c );                                               
        (a.*pg)( d );                                               

        return 0;                                                   
}                                                              
4

4 回答 4

2

您尝试做的事情不能在 C++ 中合法地完成。C++ 不支持函数参数类型的任何协变或逆变,无论这是成员函数还是自由函数。

在您的情况下,实现它的正确方法是引入一个用于参数类型转换的中间函数

class A 
{                                                           
public:          
  ...                                                   
  void f( C& c ) { printf("%c\n",c.c); }                      
  void f_with_D( D& d ) { f(d); }
  ...
};          

并使您的指针指向该中间函数而不进行任何强制转换

void (A::*pg)( D& d ) = &A::f_with_D;

现在

A a;
D d;                                                        
(a.*pg)( d );

最终将a.f使用C对象的子对象d作为参数调用。

编辑:是的,它适用于函数重载(如果我正确理解你的问题)。您只需要记住,对于函数重载,为了将内部调用定向到正确版本的函数,您必须使用显式强制转换

class A 
{                                                           
public:          
  ...                                                   
  void f( C& c ) { printf("%c\n",c.c); }                      
  void f( D& d ) { f(static_cast<C&>(d)); }
  ...
};          

如果没有演员表,你最终会A::f(D&)递归地调用自己。

于 2011-05-31T19:16:35.663 回答
1

编译器应该拒绝带有您编写的 dynamic_cast 的代码。(我认为这是一个错字,你的意思是 reinterpret_cast 考虑到你的介绍文本)。

使用 reinterpret_cast,您不会处于定义明确的情况之一(主要涉及转换为另一种类型,然后再返回原始类型)。因此,对于转换结果,我们处于未指定的领域,而对于调用转换结果时的行为,我们处于未定义的领域。

于 2011-05-31T19:00:25.993 回答
1

不,您的示例无法正常工作。
首先,您只能使用dynamic_cast相关的类类型之间进行转换,而不是其他东西。
其次,即使您将其替换dynamic_castreinterpret_cast或 C 风格的演员表(我假设您的意思),我也会得到以下输出:

cc
_

不是你想要的。

为什么它甚至可以工作并且不会严重崩溃是因为在成员函数指针之间来回转换是“安全的”,不会丢失任何信息。
为什么它仍然打印一些东西是因为编译器没有看到类型的错误,但是程序集不关心类型,它只关心地址,所以它仍然会调用A::f,因为这是你保存的指针,不管类型如何.

有趣的是,即使您取消了类的关联(D不继承自C),这仍然有效,同样因为汇编不关心类型。通过以下方式更改 A 中的功能:

void f( C& c ) { printf("f(C& c): %c\n",c.c); }
void g( D& d ) { printf("g(D& d): %c\n",d.d); }

导致以下输出:

f(C&c):c
f(C&c):d

“那是怎么回事?D甚至没有c会员!”。好吧,又是因为地址。两个变量与this指针的偏移量相同,即+0. 现在让我们将另一个成员放入C(简化类):

struct C{
        C () : c('c') {}
        int i; // mean
        const char c;
};

再试一次,输出:

f(C&c): c
f(C&c): ╠</p>

是的,我们走了。C::c现在位于偏移量+4( +0+ sizeof int),并printf从那里读取。在D中,没有这样的偏移并printf从未初始化的内存中读取。另一方面,访问未初始化的内存是未定义的行为

所以,最后得出结论:不,这不安全。:)

于 2011-05-31T19:02:00.143 回答
0

您需要使用 areinterpret_cast使其工作。D在这种情况下它应该是安全的(参见备注),但如果使用多重继承,它可能会失败,因为在将 a作为 a传递时需要调整指针C。编译器需要知道这必须发生,这在这种情况下是不可能的(pg使用 a调用d会跳过这一步,成员函数将获得D对象的未修改地址)。

备注:我说的是安全的 - 好吧,实际上它是未定义的行为,因为将类型重新解释为不相关的类型并使用该类型,但它仍然应该适用于大多数编译器。请不要在生产代码中这样做。

于 2011-05-31T18:57:11.317 回答