C ++ «простой» обратный вызов функции для функций с произвольными аргументами и возвращаемым значением - PullRequest
1 голос
/ 09 марта 2012

Я пытаюсь создать простую функцию таймера / профилировщика. Моя главная цель - создать функцию, которую я могу быстро, легко и незаметно добавить к любым вызовам функций, которые я хочу профилировать.

Так, например, для профилирования 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 обрабатывает все сроки и протоколирование и т. Д.

Я сталкивался с несколькими потоками, в которых упоминается, как выполнять обратные вызовы функций для функций с произвольными аргументами, но не так много, в которых даже упоминается, как обрабатывать возвращаемое значение.

Это самое близкое: Переменное число аргументов (va_list) с обратным вызовом функции? , но я завязал узлы при попытке обработать пустые функции, а также функции, возвращающие значение.

Я начинаю думать, что использование макросов - это способ сделать это, но, возможно, есть лучший способ.

Вот моя текущая слабая попытка (сокращенно):

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».

Но я, вероятно, все равно иду в неправильном направлении.

Ответы [ 2 ]

3 голосов
/ 09 марта 2012

Шаблоны 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);

Однако, если функции перегружены, все становится интересным.Обратите внимание, что любая обработка после вызова просто переходит в деструктор объекта, созданного до вызова.

2 голосов
/ 10 марта 2012

Ответ Дейтмара был 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);
}

Затем выполняются вызовы функций:

Для метода 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");
}
...