【问题标题】:C++ "simple" function callback for functions with arbitrary arguments and return value具有任意参数和返回值的函数的 C++“简单”函数回调
【发布时间】:2012-03-09 13:25:24
【问题描述】:

我正在尝试创建一个简单的函数计时器/分析器。我的主要目标是创建一个函数,我可以快速、轻松、不显眼地添加到我想要分析的任何函数调用中。

例如分析foobarbaz我想要一个ft(函数计时器)函数,它可以执行以下操作,尽可能少地影响原始代码:

ft(foo());
ft(bar(1, 2, 3));
int result = ft(baz());
string result = ft(qux("a", 2, 3.4));

注意,在bazqux 的情况下,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'"。

但无论如何,我可能走错了方向。

【问题讨论】:

  • 在 c++11 中有一个不错的 std::function。在c++03中,做起来很头疼,因为你必须创建各种模板类和函数
  • @VJovic: 各种,如非类型模板参数、表达式模板、类型特征、奇怪重复的模板模式,...,?
  • 没有一种叫做 C/C++ 的语言。
  • 我知道没有称为 C/C++ 的语言,但我用它表示我会重视基于 C 或 C++ 的实现。
  • 我会建议一种不同的方法来进行分析,就像在 Zoom 中所做的那样。您需要能够读取调用堆栈,并在中断时执行。如果采集了 100 个样本,其中 40% 出现了一条线,那么如果你可以消除这条线,总时间将减少 40%。 (这是 1.67 或 67% 的加速。)因此,它不仅可以找到昂贵的功能,还可以找到昂贵的线路。它不需要超级准确或超级高效。无论如何它都会找到它们,并且递归丝毫不会打扰它。

标签: c++ callback compiler-errors profiling function-pointers


【解决方案1】:

可变参数模板是要走的路!在任何情况下,您都需要稍微更改函数的调用方式:

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

不过,当函数重载时,事情就会变得有趣。请注意,调用之后的任何处理都只会进入调用之前设置的对象的析构函数。

【讨论】:

  • 注意OP没有说他可以使用c++11的特性
  • @VJovic:他没有另外提到...... C++11 是当前的标准。
  • @VJovic:他也没有说他做不到,而 C++11 恰好是 当前 标准。这是一个在 C++11 中很容易解决的特殊问题,而在 C++03 中由于不同的原因而很痛苦。如果没有可变参数模板,您将需要手动提供所有重载(boost 预处理器可能会有所帮助,但它不是 simple 编写/读取/维护的代码),并且没有 rvalue-references 您不能具有完美的转发,这意味着您不能完全模仿原始代码的语义,除非您愿意实现 cv 组合的指数可能性。
  • @VJovic:我没有理由假设正在使用非标准编译器(嗯,除了没有这样的小细节)。即使他不能使用这个解决方案,也值得指出一个合适的解决方案应该是什么样子。如果他需要 C++ 2003 解决方案,他将不得不接受更多的变形以获得可靠的解决方案。
  • C++11 引入这些新特性是有充分理由的。在 C++98 中有一些合理的函数(例如 this)需要不合理的代码量。
【解决方案2】:

Deitmar 的回答是 95% 的解决方案,但以下是我为使其适用于我的情况所做的调整:

  1. 为了支持具有 void 返回类型的函数,我需要为此案例添加一个特定的模板。
  2. 我需要调用方法而不是纯函数,因此我需要调整函数的引用方式。
  3. 可能是因为 2 我发现我需要传入正在调用该函数的对象。也许这实际上不是必需的,但这是我可以让它工作的唯一方法。
  4. 我想在函数回调之后、ft 返回之前做一些工作,因此对代码进行了一些简单的修改。
  5. 我需要为每次调用传递附加信息,即函数名称字符串,以便我可以进行合理的日志记录。

这是工作代码(节略)。

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

然后像这样进行函数调用:

对于带有void Foo::bar(int arg);签名的this对象的方法

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

【讨论】:

    猜你喜欢
    • 2020-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-13
    • 2012-02-28
    相关资源
    最近更新 更多