14

前提

我正在使用提供以下接口的 C 库(来自 C++):

void register_callback(void* f, void* data);
void invoke_callback();

问题

现在,我需要将函数模板注册为回调,这给我带来了问题。考虑以下代码:

template <typename T> void my_callback(void* data) { … }

int main() {
    int ft = 42;
    register_callback(reinterpret_cast<void*>(&my_callback<int>), &ft);
    invoke_callback();
}

这给了我以下链接器错误(在 OS X 上使用 g++ (GCC) 4.5.1,但适用于编译器版本/平台的大多数其他组合):

架构 x86_64 的未定义符号:

"void my_callback<int>(void*)", referenced from:  
  _main in ccYLXc5w.o

我觉得可以理解。

第一个“解决方案”</h2>

这很容易通过显式实例化模板来解决:

template void my_callback<int>(void* data);

不幸的是,这不适用于我的真实代码,因为回调是函数模板中注册的,而且我不知道该函数将针对哪一组模板参数调用,因此我无法为所有他们(我正在编写一个库)。所以我的真实代码看起来有点像这样:

template <typename T>
void do_register_callback(T& value) {
    register_callback(reinterpret_cast<void*>(my_callback<T>), &value);
    // Other things …
}

int main() {
    int ft = 42;
    do_register_callback(ft);
    invoke_callback();
}

第二个“解决方案”</h2>

通过调用函数隐式实例化函数模板。所以让我们这样做,但要确保调用没有实际执行(该函数有副作用):

template <typename T>
void do_register_callback(T& value) {
    if (false) { my_callback<T>(0); }
    register_callback(reinterpret_cast<void*>(my_callback<T>), &value);
}

似乎有效,即使启用了优化(以便编译器删除死分支)。但我不确定这是否有一天会崩溃。我还发现这是一个非常丑陋的解决方案,需要一个长度的解释性注释,以免某些未来的维护者删除这个明显不必要的代码。

问题

如何实例化我不知道模板参数的模板?这个问题显然是无稽之谈:我不能。——但是有什么偷偷摸摸的方法吗?

除此之外,我的解决方法能保证成功吗?

奖金问题

代码(特别是我将函数指针转换为 的事实void*)也会产生以下警告:

ISO C++ 禁止在指向函数的指针和指向对象的指针之间进行转换

编译时使用-pedantic. 我可以以某种方式摆脱警告,而无需为库编写强类型 C 包装器(在我的情况下这是不可能的)?

在 ideone 上运行代码(添加强制转换以使其编译)

4

3 回答 3

8

static_cast显然,真正的问题是我的原始代码中缺少:

register_callback(reinterpret_cast<void*>(&my_callback<int>), &ft);

编译得很好,但在使用 GCC 4.5 时会触发 liker 错误。使用 GCC 4.2 时它甚至无法编译,而是给出以下编译错误:

上下文信息不足,无法确定类型

一旦提供了这个“上下文信息”,代码就会编译并链接

register_callback(reinterpret_cast<void*>(
    static_cast<void(*)(void*)>(my_callback<int>)), &value);

我不知道是否真的需要演员表以及(如果是的话)为什么 GCC 4.5 允许我将其关闭,然后无法实例化模板。但至少我得到了编译代码而无需求助于黑客。

于 2011-07-18T15:01:52.673 回答
5

这应该有效:

template <typename T>
void do_register_callback(T& value) {
   void (*callback)(void*) = my_callback<T>;
   register_callback(reinterpret_cast<void*>(callback), &value);
}

第一行强制编译器实例化该函数以生成地址——然后您可以愉快地传递该地址。

编辑:让我在这个组合中加入另一个选项。制作my_callback类模板的静态成员 - 类似于以下内容:

template <typename T>
struct foo
{
static void my_callback(void* data) {
    T& x = *static_cast<T*>(data);
    std:: cout << "Call[T] with " << x << std::endl;
}
};

现在,在您的注册人员中,您甚至不需要“演员”。

template <typename T>
void do_register_callback(T& value) {
   register_callback(reinterpret_cast<void*>(&foo<int>::my_callback), &value);
}

实例化类模板的规则似乎与函数模板不同——即获取类成员的地址,实例化类型。

于 2011-07-18T14:44:47.893 回答
4

POSIX 推荐以下方式在函数指针类型和对象指针类型之间进行转换(在 C99 中未定义):

typedef void function_type(void*);
function_type *p_to_function = &my_callback<T>;
void* p_to_data = *(void**)&p_to_function;

// Undefined:
// void* p_to_data = (void*)p_to_function;

请注意,在 C++ 领域,这将执行reinterpret_cast<void**>(&p_to_function)from function_type**。这不是未定义的,而是实现定义的,不像reinterpret_cast<void*>(p_to_function). 因此,编写依赖于实现的符合 C++ 的代码可能是您最好的选择。

于 2011-07-18T15:25:23.683 回答