我怎样才能摆脱这个reinterpret_cast,或者这个用法可以吗?

发布于 2024-12-23 12:21:56 字数 966 浏览 2 评论 0原文

我有一个具有以下签名的模板成员函数:

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_castvoid (*)(std::__1::basic_string*)dispatch_function_t (又名 不允许 void (*)(void *))。

dispatch_function_t 是 C 类型,与 void (*)(void*) 相同。


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

I have a template member function with this signature:

template<typename T> void sync(void (*work)(T*), T context);

It can be called with a pointer to a function that accepts an argument of type T*. context is passed to that function. The implementation is this:

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

It uses reinterpret_cast<> and it works. The problem is that the standard doesn't define it very well and it is very dangerous. How can I get rid of this? I tried static_cast but that gave me a compiler error:

static_cast from void (*)(std::__1::basic_string<char> *) to dispatch_function_t (aka void (*)(void *)) is not allowed.

dispatch_function_t is a C type and is the same as void (*)(void*).


I'm not sure I was clear enough. What dispatch_sync_f does is it calls a given callback function and passes the given context parameter to that callback function. (It does that on another thread, although that is out of the scope of this question.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

醉生梦死 2024-12-30 12:21:56

static_cast 不支持这一点的原因是它是
可能不安全。而 std::string* 将隐式转换为
a void*,两者不是一回事。正确的解决方案是
为您的函数提供一个简单的包装类,该类采用 void*
static_cast将其返回到所需的类型,并传递地址
这个包装函数到你的函数。 (实际上,在现代
机器,你将摆脱reinterpret_cast,因为所有
指向数据的指针具有相同的大小和格式。无论你想剪吗
像这样的角落取决于你——但在某些情况下,它是
有理有据。我只是不相信这是其中之一,因为
简单的解决方法。)

编辑:还有一点:你说 dispatch_function_t 是一种 C 类型。如果是这种情况,实际类型可能是 extern "C" void (*)(void*),并且您只能使用具有 "C" 的函数来初始化它> 联动。 (同样,您可能会侥幸逃脱,但我使用的编译器的调用约定对于 "C""C++" 是不同的。)

The reason this is not supported by static_cast is because it is
potentially unsafe. While a std::string* will convert implicitely to
a void*, the two are not the same thing. The correct solution is to
provide a simple wrapper class to your function, which takes a void*,
and static_casts it back to the desired type, and pass the address of
this wrapper function to your function. (In practice, on modern
machines, you'll get away with the reinterpret_cast, since all
pointers to data have the same size and format. Whether you want to cut
corners like this is up to you—but there are cases where it's
justified. I'm just not convinced that this is one of them, given the
simple work-around.)

EDIT: One additional point: you say that dispatch_function_t is a C type. If this is the case, the actual type if probably extern "C" void (*)(void*), and you can only initialize it with functions that have "C" linkage. (Again, you're likely to get away with it, but I've used compilers where the calling conventions were different for "C" and "C++".)

独孤求败 2024-12-30 12:21:56

我想,您不仅将 work 转换为 dispatch_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>);
}

I guess, you are not only casting work to dispatch_function_t, but calling it through dispatch_function_t pointer, aren't you? Such cast itself is valid according to standard, but all you can do with a casted pointer is cast it back to original type. Still your approach should work with most compilers and platforms. If you'd like to implement it so it's more standard conforming you can make a wrapper for your context and work function like this:


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

临走之时 2024-12-30 12:21:56

我不敢相信这有效,或者你对“这有效”的定义相当狭窄(例如,你发现了一个特定的设置,它似乎做了你认为应该做的事情)。我不清楚 dispatch_sync_f() 的作用,但我认为它获取指向局部变量 context 的指针作为参数是可疑的。假设此变量的寿命超过了此指针的使用期限,则仍然存在一个微妙的问题,该问题不会让您在大多数平台上使用,但会在某些平台上使用:

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

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

I can't believe this works or you have a rather narrow definition of "this works" (e.g. you found one particular setup where it seems to do what you think it should do). I'm not clear what dispatch_sync_f() does but I think it is suspicious that it gets a pointer to the local variable context as parameter. Assuming this variable outlives the use of this pointer, there is still a subtle problem which won't get you on most platforms but does get you on some:

C and C++ calling conventions can be different. That is, you cannot cast a pointer to a C++ function to a pointer to a C function and hope for this to be callable. The fix to this problem - and your original question - is, of course, an extra level of indirection: don't dispatch to the function you get as argument but rather dispatch to a C function (i.e. a C++ function declared as extern "C") which takes its own context holding both the original context and the original function and calls the original function. The only [explicit] cast needed is the static_cast<>() restoring a pointer to your internal context from the void*.

Since you seem to implement a template you might need to use another indirection to get rid of this type: I don't thing function templates can be declared extern "C". So you would need to restore the original type somehow e.g. using a base class and a virtual function or something like std::function<void()> holding a readily callable function object doing this conversion (a pointer to this object would be your context).

酒解孤独 2024-12-30 12:21:56

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

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

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

I believe the cast to/from these two function pointer types is fine:

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

The problem is that you can't actually use the pointer that you have so cast. It's legal only to cast back to the original type (and those casts are reinterpret_cast, because these are unrelated types). From your code, I can't see how your actual callback function is defined. Why can't you accept a dispatch_function_t as your parameter for queue::sync, rather than casting it?

辞别 2024-12-30 12:21:56

在从类型 T *void * 类型相互转换时,reinterpret_cast 保证正常工作。但是,从指向 T 的基类或派生类的指针进行转换是不可接受的。

在这种情况下,work 的类型需要为 dispatch_function_t,并且该函数中的首要任务是从 void *T * 的转换。不允许使用不同的参数类型隐式转换参数和转换函数类型。

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

reinterpret_cast is guaranteed to work when converting from a type T * to void * and back. It is, however, not acceptable to cast from or to a pointer to a base or derived class of T.

The type of work needs to be dispatch_function_t in this case, and the first order of business in that function needs to be the cast from void * to T *. Implicitly casting the argument by using a different argument type and casting the function type is not allowed.

Rationale: the standard allows different pointer representations for different types, as long as all pointer types can be converted to void * and back, so void * is the "most precise" pointer type. A conforming implementation is allowed to clear the bottom-order bits of an uint32_t * if sizeof(uint32_t) > sizeof(char) (i.e. sizeof(uint32_t) > 1) or even shift the pointer value if the machine instructions can utilize these pointers more effectively; on a machine with tagged or shifted pointer values the reinterpret_cast is not necessarily a no-op and needs to be written explicitly.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文