通用函数调度机制

时间:2015-08-03 16:18:17

标签: c++ templates c++14

我正在尝试编写一个通用的函数调度机制,在调用实际函数之前我会做一些额外的工作(例如,执行函数的时间)。以下代码有效,但类型void f(....)的函数除外,因为我们声明了ret。

#define execute(fn, ...)    exec_helper(#fn, fn, ##__VA_ARGS__)
#define execute0(fn)        exec_helper(#fn, fn)

template <typename TASK, typename... ARGS>
auto exec_helper(const char *fn_name, TASK&& task, ARGS&&... args)
{
      //std::function<std::result_of_t<TASK(ARGS...)> ()> func
      //           = std::bind(std::forward<TASK>(task),
      //                       std::forward<ARGS>(args)...);

#ifdef TIME_FUNC
      auto start = std::chrono::steady_clock::now();
#endif

      auto ret = task(std::forward<ARGS>(args)...);

#ifdef TIME_FUNC
      auto end = std::chrono::steady_clock::now();
      auto diff = end - start;
      auto time = std::chrono::duration<double, std::milli>(diff).count();
      std::cout << "\n" << fn_name << "\t = "
                << std::setprecision(3) << time << " ms\n";
#endif

      return ret;
}

有没有办法让它适用于这些类型的功能?也许使用一些模板技巧?

5 个答案:

答案 0 :(得分:5)

C ++的一个有趣特性是,你可以这样写:

return f();

即使f的返回类型为void。这样,您实际上可以避免使用ret变量。

记住这一点,以及析构函数是如何工作的,你可以将任务前工作和后续工作包装在一个类中 - 并分别在构造函数和析构函数中调用它们,如下所示:

struct watch
{
     extern watch(char const *fn_name) 
            : _fn_name(fn_name),
              _start(std::chrono::steady_clock::now()) {}

    ~watch()
     {
       auto end = std::chrono::steady_clock::now();
       auto diff = end - _start;
       auto time = std::chrono::duration<double, std::milli>(diff).count();
       std::cout << "\n" << _fn_name << "\t = "
                << std::setprecision(3) << time << " ms\n";
     }

     char const *_fn_name;
     decltype(std::chrono::steady_clock::now()) _start;
};

并将其用作:

template <typename TASK, typename... ARGS>
auto exec_helper(const char *fn_name, TASK&& task, ARGS&&... args)
{
 #ifdef TIME_FUNC
      watch start_watching { fn_name };
 #endif

     return task(std::forward<ARGS>(args)...);
}

希望有所帮助。

您甚至可以将包装类概括为:

struct invariant_executor //choose a more general name now!
{
   using F = std::function<void()>;

   invariant_executor(F before, F after): _after(std::move(after)) 
   {
       before(); 
   }

   ~invariant_executor()
   {
       _after(); 
   }
   F _after;
};

并将其用作:

template <typename TASK, typename... ARGS>
auto exec_helper(const char *fn_name, TASK&& task, ARGS&&... args)
{
 #ifdef TIME_FUNC
      decltype(std::chrono::steady_clock::now()) start;
      invariant_executor local 
      {
         [&] {  start = std::chrono::steady_clock::now(); },

         [&] {
            auto end = std::chrono::steady_clock::now();
            auto diff = end - start;
            auto time = std::chrono::duration<double, std::milli>(diff).count();
            std::cout << "\n" << fn_name << "\t = "
                << std::setprecision(3) << time << " ms\n";
         }
      };
 #endif

     return task(std::forward<ARGS>(args)...);
}

现在,您可以灵活地在构造函数和invariant_executor的析构函数中执行任何代码。

答案 1 :(得分:1)

您可以使用packaged_task

20

这已经处理了std::packaged_task<Sig> pack_task(std::forward<Task>(task)); auto fut = pack_task.get_future(); // pre-time stuff // run task pack_task(std::forward<Args>(args)...); // post-time stuff // get the result - works with either void or a real type return fut.get(); 特殊情况,但现在您必须提出void,并处理构建Sig的开销。

答案 2 :(得分:0)

您可以使用std::packaged_task并等待std::future的结果可用。当然,这会产生开销。

template<typename Func, typename ... Args>
auto func(Func &&f, Args &&... args) -> decltype(f(std::forward<Args>(args)...)){
    using return_type = decltype(f(std::forward<Args>(args)...));

    std::packaged_task<return_type(Args&&...)> task(f);
    auto future = task.get_future();

    // do something

    task(std::forward<Args>(args)...);

    // do something else

    return future.get();
}

然后你的函数可以有任何返回类型,甚至是void

答案 3 :(得分:0)

您可以让exec_helper()分派给两个辅助函数exec_helper2()中的一个,具体取决于返回类型是否为void。这些函数相互重载,但区别在于它们的第一个参数isvoid的类型。

#include <chrono>
#include <iomanip>
#include <iostream>
#include <utility>
#include <type_traits>

#define TIME_FUNC

#define execute(fn, ...)    exec_helper(#fn, fn, ##__VA_ARGS__)
#define execute0(fn)        exec_helper(#fn, fn)

template <typename TASK, typename... ARGS>
auto exec_helper2(std::false_type isbool,
                 const char *fn_name, TASK&& task, ARGS&&... args)
{
      //std::function<std::result_of_t<TASK(ARGS...)> ()> func
      //           = std::bind(std::forward<TASK>(task),
      //                       std::forward<ARGS>(args)...);

#ifdef TIME_FUNC
      auto start = std::chrono::steady_clock::now();
#endif

      auto ret = task(std::forward<ARGS>(args)...);

#ifdef TIME_FUNC
      auto end = std::chrono::steady_clock::now();
      auto diff = end - start;
      auto time = std::chrono::duration<double, std::milli>(diff).count();
      std::cout << "\n" << fn_name << "\t = "
                << std::setprecision(3) << time << " ms\n";
#endif

      return ret;
}

template <typename TASK, typename... ARGS>
auto exec_helper2(std::true_type isbool,
                 const char *fn_name, TASK&& task, ARGS&&... args)
{
#ifdef TIME_FUNC
      auto start = std::chrono::steady_clock::now();
#endif

      task(std::forward<ARGS>(args)...);

#ifdef TIME_FUNC
      auto end = std::chrono::steady_clock::now();
      auto diff = end - start;
      auto time = std::chrono::duration<double, std::milli>(diff).count();
      std::cout << "\n" << fn_name << "\t = "
                << std::setprecision(3) << time << " ms\n";
#endif
}

template <typename TASK, typename... ARGS>
auto exec_helper(const char *fn_name, TASK&& task, ARGS&&... args)
{
  return exec_helper2(std::is_same<std::result_of_t<TASK(ARGS...)>, void>{},
                      fn_name, std::forward<TASK>(task),
                      std::forward<ARGS>(args)...);
}

void void_f() {}
int int_f() { return 42; }

int main()
{
  execute0(void_f);
  execute0(int_f);
}

答案 4 :(得分:0)

以下几行可能对您有用:

#include <iostream>

template <typename Func, typename T> class ResultStash {
 public:
  explicit ResultStash(Func f) : m_t(f()) {}
  T get() { return std::move(m_t); }
 private:
  T m_t;
};

template <typename Func> class ResultStash<Func, void> {
 public:
  explicit ResultStash(Func f) { f(); }
  void get() { return; }
};


template <typename Func>
auto wrap_function_execution(Func f) -> decltype(f()) {
  std::cout << "Doing something before executing the function" << std::endl;

  // Call f() and remember the result
  ResultStash<decltype(f), decltype(f())> result(f);

  std::cout << "Doing something after executing the function" << std::endl;

  // Return the result of f()
  return result.get();
}

void someVoidFunction(int * a) {
  if (a) {
    *a = 5;
    std::cout << "Setting a = " << *a << std::endl;
  }
}

int someNonVoidFunction(int a) {
  int b = 10;
  std::cout << "Multiplying " << a << " by " << b << std::endl;
  return a * b;
}

int main(int argc, char * argv[]) {
  int a;
  wrap_function_execution([&a] { someVoidFunction(&a); });
  auto b = wrap_function_execution([&a] { return someNonVoidFunction(a); });
  std::cout << "b = " << b << std::endl;

  return 0;
}

这引入了一个模板类,它在包装器中保存底层函数的结果,并且专门用于void函数。在类的非void版本中使用std :: move可以在发布&#39;时最大限度地减少开销。你的包装器中隐藏的结果。

与Nawaz的方法相比,这具有以下(轻微)优势:

  • 它将包装器代码的流程保存在一个地方,而不是将其分解为包装类的构造函数和析构函数。
  • 它避免在析构函数中工作。
  • 与包装函数交互更容易导致执行后的包装器代码。

它有以下缺点:

  • 包装函数的结果必须是可移动的(虽然这通常是一件好事)。
  • 它更具侵入性,即更难评论。
  • 从RAII的角度来看,Nawaz的方法更具吸引力。

示例代码生成:

Doing something before executing the function
Setting a = 5
Doing something after executing the function
Doing something before executing the function
Multiplying 5 by 10
Doing something after executing the function
b = 50