【问题标题】:C++11 and after: How to store an object pointer and method pointer, received in a templatized method, for later use?C++11 及更高版本:如何存储在模板化方法中接收的对象指针和方法指针,以供以后使用?
【发布时间】:2020-07-20 09:36:43
【问题描述】:

问题:我有一个模板函数,它接受模板参数类的对象指针和指向该类方法的方法指针。然后我可以立即在该对象上调用该方法。但我不想想要立即调用它。相反,我想保存这两个指针以供将来使用,并在以后在不会根据上下文知道该类型是什么的代码中调用它们。

在旧版 C/C++99 代码中,我们将一个函数指针和一个 void* 用户数据指针传递给将执行回调的代码(例如,在计时器结束、用户事件等时)我们'd 几乎总是将对象指针作为用户数据传递,并编写一个单行 C 函数,将用户数据指针转换为该类型并调用对象上的方法:

void TimerCB( void* pvUserData, void* pvCallerData ) {
    ( (Foo*) pvUserData )->TimerDone( pvCallerData );
}

在 C++11 中,std::function 允许我们传入 lambda 和 std::bind,或者是没有用户数据的 C 函数。

然而,在实践中,几乎每次我都只想在当前对象上调用一个方法。我可以使用 lambda 或绑定来做到这一点,但它很冗长:

  class Timer {

      :

      virtual void SubscribeTimer( const char* pszTime,
                                   std::function<void(Data*)> pfn );
  };

  void Timer::SubscribeTimer( const char* pszTime,
                             std::function<void(Data*)> pfn ) {
      cout << "  calling std::function\n";
      Data d;
      pfn( &d );
  }

  // Inside methods of class Foo:

  SubscribeTimer( "14:59:50", std::bind( &Foo::TimerDone, this, std::placeholders::_1 ) );

  SubscribeTimer( "14:59:50", [this](Data* pdata){this->TimerDone( pdata );} );

我可以传入方法指针,如果我在编译时知道它们的对象的类,像这样:

  class Timer {

      :

      virtual void SubscribeTimer( const char* pszTime,
                                   void (Foo::*pfn)( Data* pd ), Foo* pfoo );
  };

  void Timer::SubscribeTimer( const char* pszTime, void (Foo::*pfn)( Data* pd ), Foo* pfoo ) {
      cout << "  calling method\n";
      Data d;
      (pfoo->*pfn)( &d );
 }

  // Inside methods of class Foo:

  SubscribeTimer( "14:59:50", &Foo::TimerDone, this );

但是,这是不可接受的,因为我的 Timer 类属于项目的实用程序库级别,不需要了解每个可能的用户类,例如 Foo。

好的,事实证明我可以模板化该方法,因此我不再需要知道 Foo 是什么类型的对象或该方法是一个方法。这编译没有错误。 (方法和类指针交换了,所以很清楚调用了哪个重载函数。)

  class Timer {

      :

      template<typename T> void SubscribeTimer( const char* pszTime, T* pthis,
                                                void (T::*pfn)( Data* pd ) );
  };



template<typename T> void Foo::SubscribeTimer( const char* pszTime, T* pthis,
                                                void (T::*pmethod)( Data* pd ) ) {
  cout << "  calling any method\n";
  Data d;
  (pthis->*pmethod)( &d ); // <-- PROBLEMATIC LINE
}

// Inside methods of class Foo:

SubscribeTimer( "14:59:50", this, &Foo::TimerDone );

所以... 胜利!这是我想要的更简单的语法,而不是上面显示的更混乱的 lambda 和 std::bind

但这是我的问题。 上面的示例有效,因为标记为 PROBLEMATIC LINE 的行位于编译器知道 pthis 类型的上下文中。但实际上,SubscribeTimer() 不会立即调用该回调。相反,它会保存该值以供将来参考。很久以后,如果应用程序在 14:59:50 仍然运行,则会调用该回调。

【问题讨论】:

    标签: c++11 callback function-templates pointer-to-member-functions


    【解决方案1】:

    你已经知道了答案的所有部分(std::function,lambdas);你只需要把它们放在一起。

    std::function<void(Data*)> f = [=](Data* d) { (pthis->*pmethod)(d); }
    

    现在保存这个函数,例如在数据成员中。当需要调用它时,那就是

    Data d;
    f_member(&d);
    

    【讨论】:

    • 好的,所以与其让我的客户端软件自己编写挑剔的 lambda,它们只是为我提供对象和方法,然后我的模板将其转换为我处理的 lambda,就好像他们给了我一个 lambda 一样。现在你指出来很明显!我不知道为什么我没有弄清楚。
    • 好的,我想将 T 的类型限制为 Xyz 类型的对象或子类。我希望子类能够添加基类中没有的方法,并用这个函数注册,所以我想我需要T,我不能只写xyz。但是,将使用限制为此类子类的最简洁的方法是什么?
    • 最简单的可能是static_assert(std::is_base_of_v&lt;Xyz, T&gt;); 作为SubscribeTimer 的第一行。虽然坦率地说我不明白这一点。如果您希望能够调用任何方法,包括那些不是从Xyz 继承的方法,您为什么还要关心该类是从Xyz 派生的?如果不是,什么会停止工作?
    • 这是一个多线程应用程序。实际的回调可能是一个网络数据包,它对从 Xyz 子类化的数千个对象感兴趣。它不是让获取传入数据的线程直接按顺序调用这些对象,而是将回调排队到只有 Xyz 类的对象才有的消息队列中。
    • 那么你可能需要在某个地方保存Xyz*指针,除了回调(这样你就可以从中获取消息队列)。一旦你有Xyz* p = pthis; 或类似的,这将有效地要求T 派生自Xyz,否则这一行将无法编译。
    猜你喜欢
    • 1970-01-01
    • 2015-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多