在函数指针之间转换

时间:2009-10-19 16:55:05

标签: c++ delegates callback function-pointers

我目前正在使用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强制演员,它会起作用,但它会安全吗?

PS:这些是时间关键的回调,重度虚拟导向解决方案被认为是不切实际的:/

3 个答案:

答案 0 :(得分:5)

C ++标准在13.4 / 7中说:

  

没有标准转换(第4节)指向一个指针到另一个指针。特别是,即使BD的公共基础,我们也有

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

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

答案 1 :(得分:0)

当然,转换函数指针通常是一个坏主意。

将函数指针从void(*)(Derived*)转换为void(*)(Base*)可能有效,也可能无效。如果需要在转换时调整Derived *和Base *指针的内部表示,它肯定不会起作用。但是,如果是单继承关系,则不太可能。仍然,类布局和指针调整是实现定义的,您不应该依赖于此。如果你想承担风险:去看看啊。

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

答案 2 :(得分:0)

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

如果要将回调添加到委托,,您希望它们具有不同的类型,就像您一样,您有两个方案:

  • 您在编译时知道所有代表=&gt;你可以将它们包装在一个类型列表中。

  • 代表可以在运行时组成=&gt;您需要使用运行时绑定,即虚函数(exec方法等)。