【问题标题】:Pass any function by parameter [duplicate]通过参数传递任何函数[重复]
【发布时间】:2021-04-24 22:10:21
【问题描述】:

说明

你好, 我想创建一个可以执行任何类型函数的函数,在其执行结束时指示它所花费的时间。 被调用的函数可以有返回值,也可以没有返回值,0个或多个任意类型的参数。

调用函数必须打印如下内容:

Running "myFunction" .....  
Done ! (5210ms)

基本上我想创建一个函数,通过在调用前后添加代码来调用作为参数传递的任何类型的函数。

我做了什么

现在我是这样做的。

调用函数:

template <typename T>
T callFunctionPrintTime(std::string fnName, std::function<T()> fn) {
    std::cout << ">> Running " << fnName << " ... " << std::endl;
    auto t1 = std::chrono::high_resolution_clock::now();

    //Call to the target function
    T retVal = fn();

    auto t2 = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();

    std::cout << "Done ! (" << duration << " ms)" << std::endl;

    return retVal;
}

主要

int main()
{
    //Store the function to call
    std::function<unsigned long()> fn = []() {
        return myFunction(15, 10000);
    };

    //Use of the function we are interested in
    auto i = callFunctionPrintTime("myFunction", fn);

    //The return value of myFunction can be used in the rest of the program.
    std::cout << "i: " << i << std::endl;
}

我的函数
这个函数无所谓,什么都可以。

这里我们在给定的最大时间或最大循环次数内执行一个while循环,并检索执行的循环次数。

unsigned long myFunction(long maxMs, unsigned long maxI) {
    unsigned long i = 0;
    auto tStart = std::chrono::high_resolution_clock::now();
    while (maxMs > (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - tStart).count()) &&
        maxI > i) {
        i++;
    }
    return i;
}

问题

您最好的方法是什么?我对我的代码不满意。
我不确定我是否使用正确的方法通过参数传递任何类型的函数。
此外,通过使用 lambda 表达式来存储我的函数,我无法检索被调用函数的名称。所以我必须通过参数传递它的名字。

【问题讨论】:

  • 如果代码已经按预期工作,Code Review 可能更合适。但是,请确保 red 在询问之前阅读了他们的帮助中心
  • 函数名称在运行时不可访问。你需要求助于宏巫术或类似你写的东西。你的代码看起来不错
  • 该代码看起来像我见过的所有“功能测量”代码。您可以使用一些可变模板恶作剧来避免 lambda 包装器,但这只会使其不那么通用且难以维护。
  • 没有理由将一个限制为std::function。相反,可调用对象可以是任何东西。然后,没有理由将自己限制在没有参数的函数上。模板可以为参数使用可变参数包,将其转发给函数。最后,返回void 的函数不会像这样工作,因此应该使用在自动范围内构造的辅助对象来完成计时,模板只不过是实例化辅助对象,然后是return &lt;call&gt;,使用在助手的构造函数和析构函数中完成的所有工作。结束。
  • @zkoza 这侵入了被调用的函数,它可能无法编辑,并且 不应该 被编辑只是为了进行基准测试/测试/无论如何。在调用方测量时间是正确的。我承认这是对 RAII 的巧妙使用!

标签: c++ function lambda


【解决方案1】:

我很确定对于什么是最好没有单一的答案 - 但这是我的一个小改进。因为它更通用。

#include <chrono>
#include <iostream>
#include <string>
#include <type_traits>

// enable it for invocables with any type of arguments
template <class Func, class... Args,
          std::enable_if_t<std::is_invocable_v<Func, Args...>, int> = 0>
decltype(auto) callFunctionPrintTime(std::string fnName, Func fn, Args&&... args)
{
    std::cout << ">> Running " << fnName << " ... " << std::endl;
    auto t1 = std::chrono::high_resolution_clock::now();

    //Call to the target function by forwarding the arguments to it
    decltype(auto) retVal = fn(std::forward<Args>(args)...);

    auto t2 = std::chrono::high_resolution_clock::now();
    auto duration = 
        std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();

    std::cout << "Done ! (" << duration << " ms)" << std::endl;

    return retVal;
}

或者,如果您不打算为不可调用对象进行重载(这似乎很明显,当我想到它时您不会这样做),您可以使用 static_assert 而不是 SFINAE:

template <class Func, class... Args>
decltype(auto) callFunctionPrintTime(std::string fnName, Func fn, Args&&... args)
{
    static_assert(std::is_invocable_v<Func, Args...>, "must be invocable");
    //...

测试用法:

int& a_func(int i) {
    static int rv = 0;
    rv += i;
    return rv;
}


int main() {
    int& ref = callFunctionPrintTime("a_func 1", a_func, 10);
    
    std::cout << ref << '\n';  // prints 10
    
    ref += 20;

    callFunctionPrintTime("a_func 2", a_func, 100);

    std::cout << ref << '\n';  // prints 130 (10 + 20 + 100)
}

或者拨打myFunction的一些替代方案:

std::function<unsigned long()> fn = []() { return myFunction(15, 100000); };

std::cout << callFunctionPrintTime("myFunction", fn);
std::cout << callFunctionPrintTime("myFunction",
                                   []() { return myFunction(15, 100000); });
std::cout << callFunctionPrintTime("myFunction", myFunction, 15, 100000);

一些有用的链接: decltype(auto)std::enable_if_tstd::is_invocable_vSFINAE

【讨论】:

  • 是的,模板通常比std::function 更可取,除非确实需要后者的类型擦除或模板被证明是某种瓶颈(在大多数情况下不太可能)
  • @underscore_d 是的,如果需要,这里仍然可以选择使用std::function
  • 谢谢你的回答!它回答了我的问题,而且功能是通用的。我不知道decltype(auto)std::enable_if_tstd::is_invocable_v,所以我会去看看。
  • @CharrièreMaxime 不客气!我也添加了一些指向答案的链接。
【解决方案2】:

主要思想是正确的。有一些细节可以改进:

template <typename Func, typename ... Ts>
decltype(auto) callFunctionPrintTime(std::string_view fnName, Func&& f, Ts&&... args) {
    static_assert(std::is_invocable_v<Func&&, Ts&&...>); // Possibly SFINAE instead.
    std::cout << ">> Running " << fnName << " ... " << std::endl;

    struct Finally {
        std::chrono::time_point<std::chrono::high_resolution_clock> t1 =
            std::chrono::high_resolution_clock::now();

        ~Finally() {
            auto t2 = std::chrono::high_resolution_clock::now();
            auto duration =
                std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();

            std::cout << "Done ! (" << duration << " ms)" << std::endl;
        }
    } finally;

    return std::invoke(std::forward<Func>(f), std::forward<Ts>(args)...);
}

现在:

  • 处理 void 返回类型(无需专门化)。
  • 还会记录异常情况(您可以进一步使用 std::uncaught_exceptions 或 try/catch 块将异常与正常路径分离)。
  • 使用其参数处理任何可调用对象。

对于自动命名,我们必须依赖 MACRO:

#define CallFunctionPrintTime(F, ...) callFunctionPrintTime(#F, F __VA_OPT__(,) __VA_ARGS__)

Demo

【讨论】:

  • 这确实是一个更通用的解决方案!我需要更频繁地学习使用invoke。 :)
猜你喜欢
  • 2014-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-21
  • 2019-01-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多