函数指针的类型转换

3

我目前正在使用Don Clugston的fastdelegates实现一个定时器/回调系统。(请参见http://www.codeproject.com/KB/cpp/FastDelegate.aspx

这是初始代码:

struct TimerContext
{
};

void free_func( TimerContext* )
{
}

struct Foo
{
    void member_func( TimerContext* )
    {
    }
};

Foo f;
MulticastDelegate< void (TimerContext*) > delegate;

delegate += free_func;
delegate += bind( &Foo::member_func, &f );

好的,但现在我希望用户能够通过子类化 TimerContext 来存储和发送自己的结构到回调函数中。这样做的目的是为了防止用户不得不自己向下转换 TimerContext

struct TimerContext
{
};

struct MyTimerContext : TimerContext
{
    int user_value;
};

void free_func( TimerContext* )
{
}

void free_func2( MyTimerContext* )
{
}

struct Foo
{
    void member_func( TimerContext* )
    {
    }

    void member_func2( MyTimerContext* )
    {
    }
};

Foo f;
MulticastDelegate< void (TimerContext*) > delegate;

delegate += free_func;
delegate += free_func2;
delegate += bind( &Foo::member_func,  &f );
delegate += bind( &Foo::member_func2, &f );

就像你猜测的那样,GCC不会让我这么做 :)

error: invalid conversion from `void (*)(MyTimerContext*)' to `void (*)(TimerContext*)'
error:   initializing argument 1 of `delegate::Delegate<R ()(Param1)>::Delegate(R (*)(Param1)) [with R = void, Param1 = TimerContext*]'

现在我的问题是,如果我用 reinterpret_cast 强制转换,它会起作用,但是安全吗?

顺便说一句,这些是时间敏感的回调,重型虚拟化方案被认为是不切实际的 :/

3个回答

5

C++标准第13.4/7节声明:

there are no standard conversions (clause 4) of one pointer-to-function type into another. In particular, even if B is a public base of D, we have

D* f();
B* (*p1)() = &f;  // error
void g(D*);
void (*p2)(B*) = &g;  // error

你可以使用函数适配器来存储带有一个参数的函数指针,例如boost::function,但我现在不确定它是否能解决你的问题。


好的,好吧 :( 謝謝你提供的參考! - NewbiZ
即使使用boost::function也无法在他的情况下工作,因为TimerContext不能隐式转换为MyTimerContext。我在我的答案中提到了这个类型安全问题,但由于某些原因被downvote了。 - sellibitze

0

当然,将函数指针进行强制转换通常是一个不好的想法。

将函数指针从void(*)(Derived*)转换为void(*)(Base*)可能会起作用,也可能不会。如果在转换时需要调整Derived*和Base*指针的内部表示,则肯定不起作用。但是,在单继承关系的情况下,这不太可能发生。尽管如此,类布局和指针调整是实现定义的,您不应该依赖于此。如果您想冒险:请继续。

假设指针不需要调整,则从void(*)(Derived1*)void(*)(Base*)的转换仍然不是一个好主意(不安全),因为它允许使用期望Derived1*的函数来调用Derived2*,其中Derived1和Derived2是继承层次结构中的兄弟姐妹。


1
这不是实现定义,而是未定义行为(其含义略有不同)。 - Pavel Minaev
不,你误解了。类布局和可能的指针调整是实现定义的。 - sellibitze

0

reinterpret_cast 只有在对象的“发送方”和“接收方”对应时才是安全的。因此,如果发送方和接收方由同一段代码实现,那么它可能是相当安全的,一段时间内。

如果您想向委托添加回调,并且您希望它们具有不同的类型,那么您有两种情况:

  • 您预先知道所有委托,在编译时 => 您可以将它们包装在类型列表中。

  • 委托可以在运行时组成 => 您需要使用运行时绑定,即虚函数(例如exec方法或类似方法)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接