我正在尝试创建一个简单的函数timer / profiler。我的主要目标是创建一个函数,我可以快速,轻松,不引人注意地添加到我想要分析的任何函数调用。
因此,例如,对foo
,bar
和baz
进行分析,我希望有一个ft
(函数计时器)函数可以执行以下操作,从而影响原始函数代码尽可能少:
ft(foo());
ft(bar(1, 2, 3));
int result = ft(baz());
string result = ft(qux("a", 2, 3.4));
注意,对于baz
和qux
,ft
返回的结果应该是函数本身返回的结果。
ft
处理所有时间和日志记录等。
我遇到过几个线程,提到如何为具有任意参数的函数执行函数回调,但不是很多,甚至提到如何处理返回值。
这是最接近的:Variable number of arguments (va_list) with a function callback?但是在尝试处理void函数以及返回值的函数时,我会被打结。
我开始认为使用宏是这样做的方法,但也许有更好的方法。
这是我目前的微弱尝试(删节):
static Timer fTimer;
class VoidFuncBase
{
public:
virtual void operator()() = 0;
};
class VoidFuncWrapper0 : public VoidFuncBase
{
public:
typedef void (*func)();
VoidFuncWrapper0(func fp) : fp_(fp) { }
void operator()() { fp_(); }
private:
func fp_;
};
template<typename P1>
class VoidFuncWrapper1 : public VoidFuncBase
{
public:
typedef void (*func)(const P1 &);
VoidFuncWrapper1(func fp, const P1 &p1) : fp_(fp), p1_(p1) { }
void operator()() { fp_(p1_); }
private:
func fp_;
P1 p1_;
};
template<typename P1, typename P2>
class VoidFuncWrapper2 : public VoidFuncBase
{
public:
typedef void (*func)(const P1 &, const P2 &);
VoidFuncWrapper2(func fp, const P1 &p1, const P2 &p2)
: fp_(fp), p1_(p1), p2_(p2) { }
void operator()() { fp_(p1_, p2_); }
private:
func fp_;
P1 p1_;
P2 p2_;
};
template<typename R>
class FuncBase
{
public:
virtual R operator()() = 0;
};
template<typename R>
class FuncWrapper0 : public FuncBase<R>
{
public:
typedef R (*func)();
FuncWrapper0(func fp) : fp_(fp) { }
R operator()() { return fp_(); }
private:
func fp_;
};
template<typename R, typename P1>
class FuncWrapper1 : public FuncBase<R>
{
public:
typedef R (*func)(const P1 &);
FuncWrapper1(func fp, const P1 &p1) : fp_(fp), p1_(p1) { }
R operator()() { return fp_(p1_); }
private:
func fp_;
P1 p1_;
};
template<typename R, typename P1, typename P2>
class FuncWrapper2 : public FuncBase<R>
{
public:
typedef R (*func)(const P1 &, const P2 &);
FuncWrapper2(func fp, const P1 &p1, const P2 &p2)
: fp_(fp), p1_(p1), p2_(p2) { }
R operator()() { return fp_(p1_, p2_); }
private:
func fp_;
P1 p1_;
P2 p2_;
};
template<typename R>
R ft(FuncBase<R> func, std::string functionName)
{
unsigned long threadId = getThreadId();
double startTimeMs = fTimer.getMilliseconds();
R result = func();
double duration = fTimer.getMilliseconds() - startTimeMs;
logf("%u %s took %fms", threadId, functionName.c_str(), duration);
return result;
}
void ft(VoidFuncBase func, std::string functionName, int logTimeoutMs)
{
unsigned long threadId = getThreadId();
double startTimeMs = timer.getMilliseconds();
func();
double duration = timer.getMilliseconds() - startTimeMs;
logf("%u %s took %fms", threadId, functionName.c_str(), duration);
}
目前我正在
“错误:无法将参数'func'声明为抽象类型 'VoidFuncBase'”。
但无论如何,我可能会走错方向。
答案 0 :(得分:3)
Variadic模板是要走的路!在任何情况下,您都需要稍微更改函数的调整方式:
template <typename R, typename... T, typename... A>
R ft(R (*f)(T...), A&&... a) {
// do you business
return f(std::forward<A>(a)...);
}
int r0 = ft(&baz);
int r1 = ft(&qax, a, b, c);
但是当函数重载时,事情会变得很有趣。请注意,调用之后的任何处理都会进入调用之前设置的对象的析构函数。
答案 1 :(得分:2)
Deitmar的答案是解决方案的95%,但这是我为了让它在我的案例中起作用所做的调整:
ft
返回之前,所以对代码进行了一些简单的修改。这是工作代码(删节)。
template <class C, typename R, typename... T, typename... A>
R ft(C* obj, R (C::*func)(T...), std::string functionName, A&&... args)
{
double startTimeMs = fTimer.getMilliseconds();
//extra pre-call work
R result = (obj->*func)(std::forward<A>(args)...);
//extra post-call work
double duration = fTimer.getMilliseconds() - startTimeMs;
logf("%s took %fms", functionName.c_str(), duration);
return result;
}
template <class C, typename... T, typename... A>
void ft(C* obj, void (C::*func)(T...), std::string functionName, A&&... args)
{
double startTimeMs = fTimer.getMilliseconds();
//extra pre-call work
(obj->*func)(std::forward<A>(args)...);
//extra post-call work
double duration = fTimer.getMilliseconds() - startTimeMs;
logf("%s took %fms", functionName.c_str(), duration);
}
然后进行函数调用:
对于具有签名this
void Foo::bar(int arg);
对象的方法
ft(this, &Foo::bar, "Foo::bar", (3));
当处理子类中的对象时,我必须检查涉及的对象类型。也许有一种通用的方法来做到这一点,但这不是它!:
Shape* shape = getShape();
double area = 0.0;
if(shape->getType() == SQUARE)
{
area = ft((Square*)shape, &Square::getArea, "Square::getArea");
}
else if(shape->getType() == TRIANGLE)
{
area = ft((Triangle*)shape, &Triangle::getArea, "Triangle::getArea");
}
else if(shape->getType() == CIRCLE)
{
area = ft((Circle*)shape, &Circle::getArea, "Circle::getArea");
}