我需要使用一个成员函数指针,该指针接受在其他代码中使用的基类的参数。好吧,我只想做[某事]如下面的例子。这段代码工作正常,但我想知道这样的演员是否总是安全的?我无法在此dynamic
或static
投放。
#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;
}
答案 0 :(得分: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&)
递归调用自己。
答案 1 :(得分:1)
编译器应该使用您编写的dynamic_cast拒绝代码。 (我认为这是一个错字,你的意思是考虑你的介绍文本的reinterpret_cast。)
使用reinterpret_cast,您不在一个明确定义的案例中(主要涉及转换为另一种类型,然后再转换回原始类型)。因此,我们处于未指定的转换结果领域,并且在未定义的领域中,用于调用转换结果时的行为。
答案 2 :(得分:1)
不,你的例子不能正常工作
首先,您只能使用dynamic_cast
在相关类型之间进行投射,而不是其他内容。
其次,即使用dynamic_cast
或C风格的强制转换(我假设你的意思)替换reinterpret_cast
,我也得到以下输出:
Ç
ç
不是你想要的。
为什么即使有效也不会崩溃是因为在成员函数指针之间来回转换是“安全的”,不会丢失任何信息。
为什么它仍然打印的东西是因为编译器看不到类型的错误,但程序集不关心类型,它只关心地址,所以它仍然会调用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&amp; c):c
f(C&amp; c):d
“这有什么作用?D
甚至没有c
成员!”。好吧,再次因为地址。两个变量与this
指针的偏移量相同,即+0
。现在让我们把另一个成员放在C
(类简化)中:
struct C{
C () : c('c') {}
int i; // mean
const char c;
};
再试一次,输出:
是的,我们走了。f(C&amp; c):c
f(C&amp; c):╠
C::c
现在位于偏移+4
(+0
+ sizeof int
),printf
从那里读取。在D
中,没有这样的偏移量和printf
从未初始化的内存中读取。另一方面,访问未初始化的内存是未定义的行为。
所以,最后得出结论:不,这不安全。 :)
答案 3 :(得分:0)
您需要使用reinterpret_cast
才能使其正常运行。在这种情况下它应该是安全的(参见备注),但如果使用多重继承,它可能会失败,因为在将D
作为C
传递时需要调整指针。编译器需要知道必须发生这种情况,在这种情况下是不可能的(使用pg
调用d
只会跳过此步骤,成员函数将获得{{1}的未修改地址对象)。
备注:我说安全 - 实际上它是未定义的行为,因为reinterpret_cast'类型为不相关的类型并使用该类型但它应该仍然有效在大多数编译器上。请不要在生产代码中这样做。