【问题标题】:pass userData from callback-begin to callback-end将 userData 从回调开始传递到回调结束
【发布时间】:2017-11-16 10:36:03
【问题描述】:

如何适当缓存用户callbackBegin()生成的userData,并将其发送到用户callbackEnd()

简单版(无 userData - demo

我想创建一个支持回调的复杂数据库。对于 MCVE,假设它是 MyArray

这是一个简单的数组类,支持回调但不支持userData

#include <iostream>
template<class Derived>class MyArray{           //library - I design it.
    public: void push_back(int s){
        static_cast<Derived*>(this)->callbackBegin(s);
        //do something about array
        static_cast<Derived*>(this)->callbackEnd(s);
    }
    //other fields / functions
};
class Callback : public MyArray<Callback>{      //user's class
    public: void callbackBegin(int s){
        std::cout<<"callbackBegin"<<std::endl;
    }
    public: void callbackEnd(int s){
        std::cout<<"callbackEnd"<<std::endl;
    }
};
int main() {
    Callback c;
    c.push_back(5); //print: callbackBegin callbackEnd
    return 0;
}

它工作正常。

下一步:我想将一些 userDataCallback::callbackBegin() 传递给Callback::callbackEnd()
例如,userData 是调用Callback::callbackBegin() 时的时钟时间。

我的糟糕解决方案 (void*&amp; userdata : demo)

这是我实现它的尝试:-

#include <iostream>
#include <time.h>       
template<class Derived>class MyArray{
    public: void push_back(int s){
        void* userData=nullptr;                                 //#
        static_cast<Derived*>(this)->callbackBegin(s,userData); //# ugly
        //do something about array
        static_cast<Derived*>(this)->callbackEnd(s,userData);   //# ugly
    }
};
class Callback : public MyArray<Callback>{
    public: void callbackBegin(int s,void*& userData){          //# 
        userData=new clock_t(clock());                          //# danger
        std::cout<<"callbackBegin"<<std::endl;
    }
    public: void callbackEnd(int s,void*& userData){            //#
        clock_t* userDataTyped=static_cast<clock_t*>(userData);
        clock_t clock2=clock();
        clock_t different=clock2 - (*userDataTyped);
        std::cout<<"callbackEnd time(second)="
                 <<((float)different)/CLOCKS_PER_SEC<<std::endl;
        delete userDataTyped;                                   //# danger
    }
};
int main() {
    Callback c;
    c.push_back(5); //print: callbackBegin callbackEnd time(second)=8.5e-05
    return 0;
}

它也可以正常工作,但我认为这是一个糟糕的设计(在各种#):-

  • new/delete 在 2 个地方:潜在的内存泄漏。
    强指针是首选,但我不知道如何。
  • static_cast&lt;clock_t*&gt;(userData) 是代码味,至少对我来说是这样。
  • (小问题)一个额外丑陋的参数void*&amp;

问题:什么是设计模式/C++ 魔法可以避免此类问题,同时使MyArray 简洁、易于使用、可维护(即不比简单版本差多少)?

其他说明:

  • 在实际情况下,userData
    因此,我觉得很不情愿添加void&amp;* 作为额外参数。
    澄清:(已编辑) 少数情况通常需要不同类型的userData 例如Callback1需要clock_tCallback2需要std::string

  • 建议的解决方案应避免使用std::function&lt;&gt;virtual function,因为性能是这里的主要问题。

谢谢。

【问题讨论】:

  • 1) 您的基类和派生类是紧密耦合的,其中基类具有派生的知识,因此您可能需要考虑重新设计 2) 将 userdata 设为 Derived 有什么问题?
  • @mnistic 1) MyArray 只知道 Derived 有 2 个具有特定签名的函数。我认为这是回调中的常见模式,我不认为回调是紧密耦合的。 2) 每个回调实例是userData,而不是每个调用。多线程将变得不可能。无论如何,这是一个我从未想过的好主意。谢谢!
  • @cppBeginner 任何人都可以使用静态为所有实例创建一个userData
  • @Nikita 我认为每 1 次调用 push_back() 需要 1 个 userData 实例,而不是每 1 个 Callback 实例或 1 个程序实例。如果不清楚,请见谅。

标签: c++ callback c++14


【解决方案1】:

通过void 指针传递数据是一个很好的 C 解决方案,但(恕我直言)不是 C++(特别是:不是 C++11/c++14/C++17,使用 auto 和 @987654323 @) 不错。

所以我建议从callbackBegin() 返回一个值并将该值作为第一个参数传递给`callbackEnd();像

  auto r = static_cast<Derived*>(this)->callbackBegin(s);

  static_cast<Derived*>(this)->callbackEnd(r, s);

观察(C++11 和更新的魔法)使用auto 作为callbackBegin() 返回值的类型,你可以从不同的`callbackBegin() 返回不同的类型。

额外建议:在MyArray::push_back() 中更通用:使用可变参数模板,无需固定callbackBack()callbackEnd() 接收的参数的数量和类型。

使用可变参数模板可以修改push_back()如下

template <typename ... Args>
void push_back (Args const & ... args)
 {
   auto r = static_cast<Derived*>(this)->callbackBegin(args...);

   static_cast<Derived*>(this)->callbackEnd(r, args...);
 }

以下是具有两个不同回调类(具有不同数量的参数和不同的返回类型)的完整工作示例

#include <tuple>
#include <iostream>

template <typename derT>
struct myA
 {
   template <typename ... Args>
   void push_back (Args const & ... args)
    {
      auto r = static_cast<derT*>(this)->callbackBegin(args...);

      static_cast<derT*>(this)->callbackEnd(r, args...);
    }
 };

struct cb1 : public myA<cb1>
 {
   int callbackBegin (int s)
    { std::cout << "cb1 b" << std::endl; return s+5; }

   void callbackEnd (int r, int s)
    { std::cout << "cb1 e -" << r << ", " << s << std::endl; }
 };

struct cb2 : public myA<cb2>
 {
   std::tuple<std::string, int> callbackBegin (std::string const & name,
                                               int num)
    { std::cout << "cb2 b" << std::endl; return {name+";", num+1}; }

   void callbackEnd (std::tuple<std::string, int> const &,
                     std::string const & name, int num)
    { std::cout << "cb2 e -" << name << ", " << num << std::endl; }
 };

int main ()
 {
   cb1 c1;
   c1.push_back(5);

   cb2 c2;
   c2.push_back("string arg", 7);

   return 0;
 }

【讨论】:

    【解决方案2】:

    std::any 将允许您持有 clock_t(或任何其他)对象并取消 void* 指针,但这是 C++17 的概念,尚未广泛使用(尽管有诸如 @ 之类的实现987654323@)。

    与此同时,您的代码可能会受益于一些组合而不是继承,因为数组和回调在概念上非常不同,并且似乎不属于同一个继承层次结构。所以,更喜欢组合,代码可能看起来像:

    template<class T> struct ICallback
    {
        virtual void callbackBegin(int s, std::unique_ptr<T>& p) = 0;
        virtual void callbackEnd(int s, std::unique_ptr<T>& p) = 0;
    };
    
    template<class T> class MyArray
    {
    public: 
        MyArray(std::shared_ptr<ICallback<T>> cb) { callback = cb; }
        void push_back(int s)
        {
            callback->callbackBegin(s, usrDataPtr); 
            //do something about array
            callback->callbackEnd(s, usrDataPtr);
        }
    protected:
        std::shared_ptr<ICallback<T>> callback;
        std::unique_ptr<T> usrDataPtr;
    };
    
    class ClockCallback : public ICallback<clock_t>
    {
    public: 
        void callbackBegin(int s, std::unique_ptr<clock_t>& c){          
            c = std::make_unique<clock_t>(clock());
            std::cout << "callbackBegin" << std::endl;
        }
        void callbackEnd(int s, std::unique_ptr<clock_t>& c){
            clock_t clock2 = clock();
            clock_t different = clock2 - (*c);
            std::cout << "callbackEnd time(second)="
                << ((float)different) / CLOCKS_PER_SEC << std::endl;
        }
    };
    
    
    
    int main() {
        std::shared_ptr<ClockCallback> c = std::make_shared<ClockCallback>();
        MyArray<clock_t> ma(c);
        ma.push_back(7);
    
        return 0;
    }
    

    【讨论】:

      【解决方案3】:

      您可以使用智能指针来避免手动删除您的 userData

      std::unique_ptr<clock_t> userData;
      

      将其作为回调的引用传递

      void callbackBegin(int s, std::unique_ptr<clock_t> &userData)
      

      并以这种方式初始化它

      userData = std::make_unique<clock_t>(clock())
      

      【讨论】:

      • 使用此解决方案,MyArray 必须识别Derived 使用的特定类型(在本例中为clock_t)。作为硬编码的结果,MyArray 将无法用于可能想要使用不同类型的userData 的其他用户的回调(例如Callback2)。能缓解吗?
      • 你可以像template&lt;class Derived, class UserDataType&gt;这样添加一个类模板类型名并像这样std::unique_ptr&lt;UserDataType&gt;实例化你的unique_ptr
      【解决方案4】:

      您所询问的 C++ 魔法是一种称为虚拟方法的方法。虚方法是实现回调的 C++ 原生方式之一:

       class MyArray{
            public:
              void push_back(int s) {
                  const auto userData = callbackBegin(s); //# beautiful
                  //do something about array
                  callbackEnd(s, userData);               //# beautiful
              }
            private:
              virtual clock_t callbackBegin(int) const = 0;
              virtual void callbackEnd(int, const clock_t&) const = 0;
          };
          class Callback : public MyArray{
              clock_t  callbackBegin(int s) const final {        
                std::cout<<"callbackBegin"<<std::endl;
                return clock(); //# safe
              }
              void callbackEnd(int s,const clock_t& userData) const final {            //#
                 const auto different = clock() - userDataTyped;
                 std::cout << "callbackEnd time(second)=";
                 std::cout << different/CLOCKS_PER_SEC << std::endl;
                //# safe
             }
          };
      

      另一种方法是将两个可调用对象传递给 MyArray ctor 并在 push_back 方法中使用这些对象。可调用对象应存储对相关类回调方法的调用。使用 std::function 来实现那些可调用对象。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-10-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-04-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多