2

我有一个带有这个签名的模板成员函数:

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_castfrom void (*)(std::__1::basic_string<char> *)to dispatch_function_t(aka void (*)(void *)) 是不允许的。

dispatch_function_t是 C 类型,与void (*)(void*).


我不确定我是否足够清楚。它的作用是dispatch_sync_f调用给定的回调函数并将给定的上下文参数传递给该回调函数。(它在另一个线程上执行此操作,尽管这超出了此问题的范围。)

4

5 回答 5

4

不支持的原因static_cast是它可能不安全。虽然 astd::string*将隐式转换为 a void*,但两者并不是一回事。正确的解决方案是为您的函数提供一个简单的包装类,该类接受 avoid*并将static_cast其返回所需的类型,并将此包装函数的地址传递给您的函数。(在实践中,在现代机器上,你会摆脱reinterpret_cast, 因为所有指向数据的指针都具有相同的大小和格式。你是否想像这样偷工减料取决于你——但在某些情况下它是合理的。我考虑到简单的解决方法,我只是不相信这是其中之一。)

编辑:还有一点:你说那dispatch_function_t是 C 类型。如果是这种情况,则可能是实际类型extern "C" void (*)(void*),并且您只能使用具有"C"链接的函数对其进行初始化。(同样,您可能会侥幸逃脱,但我使用了调用约定与 和 不同的编译器"C""C++"

于 2011-12-30T16:35:59.393 回答
4

我想,您不仅要转换workdispatch_function_t,还要通过指针调用它dispatch_function_t,不是吗?根据标准,这种转换本身是有效的,但是您可以使用转换指针将其转换回原始类型。您的方法仍然适用于大多数编译器和平台。如果您想实现它以使其更符合标准,您可以为您的contextwork功能制作一个包装器,如下所示:


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>);
}

于 2011-12-30T16:42:28.687 回答
1

我不敢相信这行得通,或者您对“这行得通”的定义相当狭窄(例如,您发现了一个特定的设置,它似乎在做您认为应该做的事情)。我不清楚是做什么dispatch_sync_f()的,但我认为它获得一个指向局部变量的指针context作为参数是可疑的。假设这个变量比这个指针的使用时间长,仍然有一个微妙的问题不会让你在大多数平台上,但确实让你在一些平台上:

C 和 C++ 调用约定可以不同。也就是说,您不能将指向 C++ 函数的指针转换为指向 C 函数的指针并希望它是可调用的。这个问题的解决方法——以及你原来的问题——当然是额外的间接层级:不要分派给你作为参数得到的函数,而是分派给一个 C 函数(即声明为 的 C++ 函数extern "C"),它需要它自己的上下文包含原始上下文和原始函数,并调用原始函数。唯一需要的 [显式] 强制转换是static_cast<>()void*.

由于您似乎实现了一个模板,您可能需要使用另一个间接来摆脱这种类型:我不认为可以声明函数模板extern "C"。因此,您将需要以某种方式恢复原始类型,例如使用基类和虚函数或std::function<void()>持有一个易于调用的函数对象进行此转换(指向该对象的指针将是您的上下文)。

于 2011-12-30T16:44:39.667 回答
0

我相信这两种函数指针类型的转换很好:

void(*)(void*)
void(*)(T*)

问题是您实际上不能使用您如此投射的指针。只有转换回原始类型是合法的(这些转换是reinterpret_cast,因为这些是不相关的类型)。从您的代码中,我看不到您的实际回调函数是如何定义的。为什么你不能接受 adispatch_function_t作为你的参数queue::sync,而不是强制转换它?

于 2011-12-30T16:34:19.760 回答
0

reinterpret_cast保证在从一个类型来回T *转换时工作。void *然而,从或指向 T 的基类或派生类的指针转换是不可接受的。

在这种情况下,类型work需要是dispatch_function_t,并且该函数中的第一个业务顺序需要转换为 from void *to T *。不允许使用不同的参数类型隐式转换参数并转换函数类型。

基本原理:标准允许不同类型的不同指针表示,只要所有指针类型都可以相互转换void *,那么void *“最精确”的指针类型也是如此。uint32_t *如果机器指令可以更有效地利用这些指针,则允许一致的实现清除if sizeof(uint32_t) > sizeof(char)(ie )的低位,sizeof(uint32_t) > 1甚至移动指针值;在具有标记或移位指针值的机器上,reinterpret_cast不一定是空操作,需要显式编写。

于 2011-12-30T16:37:41.327 回答