将userData从callback-begin传递给callback-end

时间:2017-11-16 10:36:03

标签: c++ callback c++14

如何正确缓存从用户callbackBegin()生成的 userData ,并将其发送给用户的callbackEnd()

简易版(无用户数据 - 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;
}

它正常工作。

下一步:我想将{strong> userData Callback::callbackBegin()传递到Callback::callbackEnd()
例如, userData 是调用Callback::callbackBegin()时的时钟时间。

我的解决方案不好(void*& userdatademo

以下是我尝试实施它: -

#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在两个地方:潜在的记忆泄漏 强指针是首选,但我不知道如何。
  • static_cast<clock_t*>(userData)是代码嗅觉,至少对我而言。
  • (小问题)一个额外的丑陋参数void*&

问题:什么是设计模式/ C ++魔术来避免此类问题,而make MyArray简洁,易用,可维护(即比简单版本差很多)?

其他说明:

  • 在实际情况中,&lt; 5%的用户回调类需要 userData
    因此,我觉得非常不愿意将void&*作为额外参数添加 澄清:(已编辑)少数群体案例通常需要不同类型的 userData ,例如Callback1需要clock_tCallback2需要std::string等。

  • 建议的解决方案应限制使用std::function<>virtual function,因为此处的性能是一个主要问题。

感谢。

4 个答案:

答案 0 :(得分:1)

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

所以我建议从std::tuple返回一个值,并将值作为第一个参数传递给`callbackEnd();

之类的东西
callbackBegin()

观察(C ++ 11和更新的魔法)使用 auto r = static_cast<Derived*>(this)->callbackBegin(s); static_cast<Derived*>(this)->callbackEnd(r, s); 作为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...);
 }

答案 1 :(得分:1)

std::any将允许您保留clock_t(或任何其他)对象并取消void *指针,但这是一个C ++ 17概念,但尚未广泛使用(虽然有boost::any)等实现。

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

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

答案 2 :(得分:0)

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

std::unique_ptr<clock_t> userData;

将其作为回调的参考传递

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

并以这种方式初始化

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

答案 3 :(得分:0)

您所询问的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方法中使用这些对象。可调用对象应存储对相关类Callback方法的调用。使用std :: function来实现那些可调用对象。