[英]How can I get rid of this reinterpret_cast, or is this usage OK?
我有一个带有此签名的模板成员函数:
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
作用是调用给定的回调函数并将给定的上下文参数传递给该回调函数。 (它在另一个线程上执行此操作,但这超出了此问题的范围。)
static_cast
不支持这个原因是因为它可能不安全。 虽然std::string*
会隐含地转换为void*
,但两者并不相同。 正确的解决方案是为您的函数提供一个简单的包装类,它将void*
, static_cast
返回到所需类型,并将此包装函数的地址传递给您的函数。 (实际上,在现代的机器,你会得到摒弃了reinterpret_cast
,因为所有的数据指针具有相同的大小和格式。无论您想走捷径这样是给你,但也有在那里的理由的情况下。考虑到简单的解决方法,我只是不相信这是其中之一。)
编辑:还有一点:你说dispatch_function_t
是C类型。 如果是这种情况,实际类型如果可能是extern "C" void (*)(void*)
,并且只能用具有"C"
链接的函数初始化它。 (同样,你可能会逃脱它,但我使用的编译器的调用约定对于"C"
和"C++"
是不同的。)
我想,你不仅要将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>);
}
我无法相信这是有效的,或者你对“这个作品”有一个相当狭隘的定义(例如,你找到了一个特殊的设置,它似乎做了你认为它应该做的事情)。 我不清楚dispatch_sync_f()
做了什么,但我认为它获得一个指向本地变量context
作为参数的指针是可疑的。 假设这个变量比这个指针的使用寿命更长,那么仍然存在一个微妙的问题,它无法让你在大多数平台上获得,但它确实能让你获得一些:
C和C ++调用约定可以不同。 也就是说,您不能将指向C ++函数的指针强制转换为指向C函数的指针,并希望它可以调用。 解决这个问题的方法 - 以及你原来的问题 - 当然是一个额外的间接级别:不要调度你作为参数获得的函数,而是调度到C函数(即声明为extern "C"
的C ++函数extern "C"
)它拥有自己的上下文,同时保存原始上下文和原始函数,并调用原始函数。 唯一需要的[explicit] static_cast<>()
是static_cast<>()
从void*
恢复指向内部上下文的指针。
由于您似乎实现了一个模板,您可能需要使用另一个间接来摆脱这种类型:我不会将函数模板声明为extern "C"
。 因此,您需要以某种方式恢复原始类型,例如使用基类和虚函数或类似std::function<void()>
保存一个可以调用的函数对象进行此转换(指向此对象的指针将是您的上下文) )。
我相信这两个函数指针类型的转换很好:
void(*)(void*)
void(*)(T*)
问题是你实际上无法使用你所投射的指针。 仅返回原始类型是合法的(这些转换是reinterpret_cast
,因为它们是不相关的类型)。 从您的代码中,我看不到您的实际回调函数是如何定义的。 为什么不能接受dispatch_function_t
作为queue::sync
参数,而不是将其转换为?
当从类型T *
转换为void *
并返回时, reinterpret_cast
保证可以正常工作。 但是,从指向T或T的基类或派生类的指针进行强制转换是不可接受的。
在这种情况下, work
类型需要是dispatch_function_t
,并且该函数中的第一个业务顺序需要是从void *
到T *
。 不允许使用不同的参数类型隐式转换参数并转换函数类型。
基本原理:标准允许不同类型的不同指针表示,只要所有指针类型都可以转换为void *
和back,因此void *
是“最精确”的指针类型。 如果sizeof(uint32_t) > sizeof(char)
(即sizeof(uint32_t) > 1
),则允许一致的实现清除uint32_t *
的底部位,或者如果机器指令可以使用这些指针,则甚至移位指针值有效; 在具有标记或移位指针值的机器上, reinterpret_cast
不一定是无操作,需要明确写入。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.