如何正确缓存从用户callbackBegin()
生成的 userData ,并将其发送给用户的callbackEnd()
。
我想创建一个支持回调的复杂数据库。对于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*& 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
在两个地方:潜在的记忆泄漏
强指针是首选,但我不知道如何。static_cast<clock_t*>(userData)
是代码嗅觉,至少对我而言。void*&
问题:什么是设计模式/ C ++魔术来避免此类问题,而make MyArray
简洁,易用,可维护(即比简单版本差很多)?
其他说明:
在实际情况中,&lt; 5%的用户回调类需要 userData 。
因此,我觉得非常不愿意将void&*
作为额外参数添加
澄清:(已编辑)少数群体案例通常需要不同类型的 userData ,例如Callback1
需要clock_t
,Callback2
需要std::string
等。
建议的解决方案应限制使用std::function<>
或virtual function
,因为此处的性能是一个主要问题。
感谢。
答案 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来实现那些可调用对象。