我有一个带有此签名的模板成员函数:
template<typename T> void sync(void (*work)(T*), T context);
可以使用指向接受类型为T*
的参数的函数的指针来调用它。 context
被传递给该函数。实施是这样的:
template<typename T> void queue::sync(void (*work)(T*), T context) {
dispatch_sync_f(_c_queue, static_cast<void*>(&context),
reinterpret_cast<dispatch_function_t>(work));
}
它使用reinterpret_cast<>
并且它有效。问题是标准没有很好地定义它并且非常危险。我怎么能摆脱这个?我试过static_cast
,但这给了我一个编译错误:
static_cast
从void (*)(std::__1::basic_string<char> *)
到dispatch_function_t
(又名void (*)(void *)
)是不允许的。
dispatch_function_t
是C类型,与void (*)(void*)
相同。
我不确定我是否足够清楚。 dispatch_sync_f
的作用是调用给定的回调函数并将给定的上下文参数传递给该回调函数。 (它在另一个线程上执行此操作,但这超出了此问题的范围。)
答案 0 :(得分:4)
static_cast
不支持此原因的原因是因为它
可能不安全。虽然std::string*
会隐含地转换为。{1}}
一个void*
,两者不是一回事。正确的解决方案是
为你的函数提供一个简单的包装类,它需要void*
,
并static_cast
将其恢复为所需类型,并传递地址
这个函数的包装函数。 (在实践中,在现代
机器,你将reinterpret_cast
侥幸逃脱,因为所有
指向数据的指针具有相同的大小和格式。你是否想要削减
像这样的角落取决于你 - 但是的情况
有道理的。鉴于此,我不相信这是其中之一
简单的解决方法。)
编辑:还有一点:你说dispatch_function_t
是C类型。如果是这种情况,那么实际类型可能是extern "C" void (*)(void*)
,并且您只能使用具有"C"
链接的函数对其进行初始化。 (同样,您可能会侥幸逃脱,但我已经使用了"C"
和"C++"
的调用约定不同的编译器。)
答案 1 :(得分:4)
我想,你不仅要将work
投射到dispatch_function_t
,而是通过dispatch_function_t
指针调用它,不是吗?根据标准,这样的强制转换本身是有效的,但是使用铸造指针所做的一切都会将其转换回原始类型。您的方法仍然适用于大多数编译器和平台。如果您想要实现它,那么它更符合标准,您可以为context
和work
函数创建一个包装器,如下所示:
template <typename T>
struct meta_context_t
{
T *context;
void (*work)(T*);
};
template <typename T>
void thunk(void *context)
{
meta_context_t<T> *meta_context = static_cast<meta_context_t<T> *>(context);
meta_context->work(meta_context->context);
}
template<typename T> void queue::sync(void (*work)(T*), T context) {
meta_context_t<T> meta_context =
{
&context,
work
};
dispatch_sync_f(_c_queue, static_cast<void*>(&meta_context),
thunk<T>);
}
答案 2 :(得分:1)
我无法相信这是有效的,或者您对“此作品”有一个相当狭隘的定义(例如,您找到了一个特殊的设置,它似乎做了您认为它应该做的事情)。我不清楚dispatch_sync_f()
做了什么,但我认为它获得指向本地变量context
作为参数的指针是可疑的。假设这个变量比这个指针的使用寿命更长,那么仍然存在一个微妙的问题,这个问题不会让你在大多数平台上获得,但确实能让你获得一些:
C和C ++调用约定可以不同。也就是说,您不能将指向C ++函数的指针强制转换为指向C函数的指针,并希望它可以调用。解决这个问题的方法 - 以及你原来的问题 - 当然是一个额外的间接层:不要调度你作为参数获得的函数,而是调度到C函数(即声明为{{1的C ++函数)它拥有自己的上下文,同时包含原始上下文和原始函数,并调用原始函数。所需的唯一[显式]强制转换是extern "C"
从static_cast<>()
恢复指向内部上下文的指针。
由于您似乎实现了一个模板,您可能需要使用另一个间接来摆脱这种类型:我不能将函数模板声明为void*
。所以你需要以某种方式恢复原始类型,例如使用基类和虚函数或像extern "C"
那样持有一个可以调用的函数对象进行这种转换(指向这个对象的指针就是你的上下文)。
答案 3 :(得分:0)
我相信这两个函数指针类型的转换很好:
void(*)(void*)
void(*)(T*)
问题是你实际上不能使用你所投射的指针。仅回滚到原始类型是合法的(并且那些强制转换是reinterpret_cast
,因为这些是不相关的类型)。从您的代码中,我看不到您的实际回调函数是如何定义的。为什么你不能接受dispatch_function_t
作为queue::sync
的参数,而不是将其投射?
答案 4 :(得分:0)
reinterpret_cast
类型转换为T *
并返回时, void *
可以保证正常工作。但是,从指向T的基类或派生类的指针进行强制转换是不可接受的。
在这种情况下,work
的类型必须为dispatch_function_t
,并且该函数中的第一个业务顺序需要是从void *
到T *
的转换。
基本原理:标准允许不同类型的不同指针表示,只要所有指针类型都可以转换为void *
并返回,因此void *
是“最精确”的指针类型。如果机器指令可以更有效地利用这些指针,则允许符合的实现清除uint32_t *
if sizeof(uint32_t) > sizeof(char)
(即sizeof(uint32_t) > 1
)的底部位,或者甚至移位指针值。在具有标记或移位指针值的计算机上,reinterpret_cast
不一定是无操作,需要明确写入。