C++的寿命是多少方法指针表达式?

发布于 2025-01-19 05:30:42 字数 1169 浏览 2 评论 0原文

我有一个通用函数,可以调用它的可可。与std :: Invoke的呼叫的道德等同,除了它是Coroutine的一部分:

// Wait for some condition to be true, then invoke f with the supplied
// arguments. The caller must ensure that all references remain valid
// until the returned task is done.
template <typename F, typename Args...>
Task<void> WaitForConditionAndInvoke(F&& f, Args&&... args) {
  co_await SomeCondition();
  std::invoke(f, std::forward<Args>(args)...);
}

由于要求输入参数保持有效,因此通过WaitforCondition and Incoke 诸如lambda之类的临时性(除非返回的任务是直接co_await作为单个表达式的一部分)。即使使用Unary +将具有绑定状态的lambda转换为函数指针的lambda也是如此:功能指针本身是暂时的,Asan似乎在被销毁后正确地抱怨使用它。

我的问题是使用会员函数指针是否合法:

struct Object {
  void SomeMethod();
};

// Some global object; we're not worried about the object's lifetime.
Object object;

Task<void> WaitForConditionAndInvokeOnGlobalObject() {
  return WaitForConditionAndInvoke(&Object::SomeMethod, &object);
}

这似乎可以正常工作,但是我不清楚指针的寿命'object :: somemethod评估IS。这是否可以保证是恒定的表达,即不是暂时的?标准的哪一部分涵盖了这一点?

I have a generic function that invokes a callable it's handed; the moral equivalent of a call to std::invoke, except it's part of a coroutine:

// Wait for some condition to be true, then invoke f with the supplied
// arguments. The caller must ensure that all references remain valid
// until the returned task is done.
template <typename F, typename Args...>
Task<void> WaitForConditionAndInvoke(F&& f, Args&&... args) {
  co_await SomeCondition();
  std::invoke(f, std::forward<Args>(args)...);
}

Because of the requirement that the input arguments remain valid, it's not always legal to pass WaitForConditionAndInvoke a temporary like a lambda (unless e.g. the returned task is directly co_awaited as part of a single expression). This is true even if using unary + to convert a lambda with no bound state to a function pointer: the function pointer is itself a temporary, and asan seems to correctly complain about using it after it's destroyed.

My question is whether using a member function pointer is legal:

struct Object {
  void SomeMethod();
};

// Some global object; we're not worried about the object's lifetime.
Object object;

Task<void> WaitForConditionAndInvokeOnGlobalObject() {
  return WaitForConditionAndInvoke(&Object::SomeMethod, &object);
}

This seems to work fine, but it's unclear to me what the lifetime of the pointer that &Object::SomeMethod evaluates to is. Is this guaranteed to be a constant expression, i.e. not a temporary? What part of the standard covers this?

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

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

发布评论

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

评论(2

故事与诗 2025-01-26 05:30:42

waitforcondition和Invoke coroutine将是危险的,除非每个参数在内,包括函数f是指拥有寿命足够长的对象。例如,waitforconditionandInvoke(std :: abs,1)具有未定义的行为,因为该对象构成了用int> int prvalue expression 1 。尽管恒定的表达值可以帮助编译器以使用死对象的已知值“工作”的方式来实现编译器,但在此处没有差异。

为了解决此问题,您可以使您的函数将每个RVALUE参数移动到本地对象中:

// All rvalue arguments are moved from.
// The caller must make sure all lvalue arguments remain valid until
// the returned task is done.
template <typename F, typename Args...>
Task<void> WaitForConditionAndInvoke(F&& f, Args&&... args) {
  // local_f could be declared an lvalue reference or object,
  // but not an rvalue reference:
  F local_f(std::forward<F>(f));
  // Similarly, the template arguments to tuple are lvalue references
  // or object types.
  std::tuple<Args...> local_args(std::forward<Args>(args)...);
  co_await SomeCondition();
  std::apply(std::forward<F>(local_f), std::move(local_args));
}

或更安全,以std :: bind执行操作,然后移动/复制所有内容。调用代码可以指定使用std :: refstd :: cref的函数或函数参数作为参考而无需移动或复制。实际上,该实施仅是:

// All arguments are moved or copied from.
// A std::reference_wrapper can be used to skip the move/copy of
// the referenced object.
template <typename F, typename Args...>
Task<void> WaitForConditionAndInvoke(F&& f, Args&&... args) {
  auto bound_f = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
  co_await SomeCondition();
  bound_f();
}

That WaitForConditionAndInvoke coroutine will be dangerous unless every argument including the functor f refers to an object with lifetime long enough. For example, WaitForConditionAndInvoke(std::abs, 1) has undefined behavior because of the object materialized to initialize a reference with the int prvalue expression 1. There is no difference per the Standard for constant expression arguments here, although a constant expression value could help compilers implement it in a way which "works" using a dead object's known value.

To fix this, you could have your function move every rvalue argument into a local object:

// All rvalue arguments are moved from.
// The caller must make sure all lvalue arguments remain valid until
// the returned task is done.
template <typename F, typename Args...>
Task<void> WaitForConditionAndInvoke(F&& f, Args&&... args) {
  // local_f could be declared an lvalue reference or object,
  // but not an rvalue reference:
  F local_f(std::forward<F>(f));
  // Similarly, the template arguments to tuple are lvalue references
  // or object types.
  std::tuple<Args...> local_args(std::forward<Args>(args)...);
  co_await SomeCondition();
  std::apply(std::forward<F>(local_f), std::move(local_args));
}

Or to be even safer, do as std::bind does, and move/copy everything. The calling code can specify that the functor or functor argument(s) should be treated as a reference with no move or copy using std::ref or std::cref. In fact, that implementation is just:

// All arguments are moved or copied from.
// A std::reference_wrapper can be used to skip the move/copy of
// the referenced object.
template <typename F, typename Args...>
Task<void> WaitForConditionAndInvoke(F&& f, Args&&... args) {
  auto bound_f = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
  co_await SomeCondition();
  bound_f();
}
帅哥哥的热头脑 2025-01-26 05:30:42

正如评论者所说,这里不存在终身问题。 &Object::SomeMethod 是一个常量表达式,就像 42&WaitForConditionAndInvokeOnGlobalObject 一样。

您可能会因为将类型命名为 struct Object 而感到困惑,因此表达式 &Object::SomeMethod 看起来看起来就像涉及一个对象...但事实并非如此; Object类型的名称。该表达式中不涉及任何对象;它是一个简单的编译时常量,就像 offsetof(Object, SomeDataMember)sizeof(Object) 一样。

不涉及对象=不涉及生命周期问题。

编辑(从而完全改变我的答案):啊,aschepler 是对的,你关心的是 F&& 所引用的事物的生命周期。 f 其中

template <typename F, typename Args...>
Task<void> WaitForConditionAndInvoke(F&& f, Args&&... args) {
  co_await SomeCondition();
  std::invoke(f, std::forward<Args>(args)...);
}

是值为 &Object::SomeMethod 的临时对象,它当然在其完整表达式的末尾处终止,即在 < 末尾的分号处终止代码>返回WaitForConditionAndInvoke(&Object::SomeMethod, &object);。一旦您点击该分号,所有临时变量都会超出范围,并且对它们的任何引用(例如,在协程框架中捕获的 f )肯定会悬空。

As the commenters say, there's no lifetime issue here. &Object::SomeMethod is a constant expression, just like 42 or &WaitForConditionAndInvokeOnGlobalObject.

You might have confused yourself by naming your type struct Object, so the expression &Object::SomeMethod kind of looks like it involves an object... but it doesn't; Object is the name of a type. There is no object involved in that expression; it's a simple compile-time constant, just like offsetof(Object, SomeDataMember) or sizeof(Object) would be.

No object involved = no lifetime issues involved.

EDIT (thus completely changing my answer): Ah, aschepler is right, you're concerned with the lifetime of the thing-referred-to-by-F&& f in

template <typename F, typename Args...>
Task<void> WaitForConditionAndInvoke(F&& f, Args&&... args) {
  co_await SomeCondition();
  std::invoke(f, std::forward<Args>(args)...);
}

which is the temporary object with value &Object::SomeMethod, which of course dies at the end of its full-expression, i.e., at the semicolon at the end of return WaitForConditionAndInvoke(&Object::SomeMethod, &object);. As soon as you hit that semicolon, all of the temporaries go out of scope and any references to them (like, that f captured in the coroutine frame) are definitely dangling.

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