参考原文地址: (原创)C++11 中 function/lambda 的链式调用
我对文章的格式和错别字进行了调整,并补充并标注出了重要的部分。以下是正文。
正文
关于链式调用,比较典型的例子是 C# 中的 linq ,不过 C# 中的 linq 还只是一些特定函数的链式调用。 C++ 中的链式调用更少见因为实现起来比较复杂。C++11 支持了 lambda 和 function ,在一些延迟计算的场景下,这个链式调用的需求更强烈了。链式调用要实现的目的是,将多个函数按照前一个的输出作为下一个输入串起来,然后再推迟到某个时刻计算。 C++中,目前看到 PPL 中有这样的用法。PPL 中链式调用的例子:
int wmain()
{
auto t = create_task([]() -> int
{ return 0; });
// Create a lambda that increments its input value.
auto increment = [](int n)
{ return n + 1; };
// Run a chain of continuations and print the result.
int result = t.then(increment).then(increment).then(increment).get();
wcout << result << endl;
}
/* Output:
3
*/
例子中先创建一个 task 对象,然后连续的调用 then 函数,其实这个 then 中的 lambda 的形参可以是任意类型的,只要保证前一个函数的输出为后一个的输入就行。将这些 task 串起来之后,最后在需要的时候去计算结果,计算的过程是链式的,从最开始的函数开始计算一直到最后一个得到最终结果。这个 task 和他的链式调用
过程是很有意思的。不过 PPL 中的链式调用有一点不太好,就是初始化的 task 不能有参数,因为 wait 函数是不能接收参数的。经过一番探索,我觉得用 C++11 我可以做一个差不多链式调用的task,还可以弥补前面所说 PPL task 不太好的地方,即可以接收参数。其实关于链式调用的实现我之前有博文做了介绍,有兴趣的童孩点这里。不过我对之前的实现方式不太满意,觉得还是 PPL 这种链式调用方式更好。好了,下面看看我是如何实现和 PPL 类似的,可以实现链式调用的 task 。
template <typename T>
class Task;
template <typename R, typename... Args>
class Task<R(Args...)>
{
public:
Task(std::function<R(Args...)> &&f) : m_fn(std::move(f)) {}
Task(std::function<R(Args...)> &f) : m_fn(f) {}
template <typename... Args>
R Run(Args &&...args) { return m_fn(std::forward<Args>(args)...); }
template <typename F> ///< 通过 std::result_of<F(R)>::type 推断出的类型作为新的返回值类型
auto Then(F &f) -> Task<typename std::result_of<F(R)>::type(Args...)> ///< Task 是尾随返回类型
{
/// @note 传入 f 到 lambda 表达式中调用执行(m_fn 的执行结果作为其参数)
/// 传入的 lambda 表达式整体又作为这个新的 Task 实例的成员变量【作者设计得很巧妙】
return Task<typename std::result_of<F(R)>::type(Args...)>([this, &f](Args &&...args)
{ return f(m_fn(std::forward<Args>(args)...)); });
}
private:
std::function<R(Args...)> m_fn;
};
测试代码:
void TestTask()
{
Task<int(int)> task = [](int i)
{ return i; };
auto result = task.then([](int a)
{ return i + 1; })
.then([](int a)
{ return i + 2; })
.then([](int a)
{ return i + 3; })
.run(1);
//result 7
}
可以看到,我的 Task 调用方式和 PPL 一致,更棒的是还可以接收参数了。也许有人有点疑惑,链式调用这个东西真的有用吗?至少目前看到 PPL 和 TBB 是这样用的,而且他对我的开发来说非常有用,至于我对他的具体应用暂且按下不表,后面就会知道我是如何用它了。