C++ feels like a new language. ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ-- Bjarne Stroustrup

说在前面

今天的标题可能不太严谨,这里我借用了Java语言中的说法,熟悉Java的朋友应该都知道方法引用的含义,这里引入Java 8 in action中的一段代码

inventory.sort(comparing(Apple::getWeight));

这里的Apple::getWeight就是我们说的方法引用,这里可以理解为我们将Apple类的方法getWeight传递给了comparing,将函数作为一等公民进行传递。

  • 正题

更准确地说,我们如何将成员方法应用到STL算法上?先看一个例子

struct Cell {
  explicit Cell(int value) : value_(value) {}
  int get_value() const { return value; }

 private:
   int value_;
};

应用

std::vector<Cell> inputs = {Cell(3), Cell(10), Cell(113)};
std::vector<int> outputs;
  
std::transform(begin(inputs), end(inputs), back_inserter(outputs), Cell::get_value); //compile error!

我们知道上面的代码是无法通过编译的,那么问题来了,在C++中怎么实现类似的东西。

昂贵的解决方案 std::function

我们可以使用C++2.0引入的std::function解决上述无法通过编译的问题

std::transform(begin(inputs), end(inputs), std::back_inserter(outputs),
               std::function<int(const Cell&)>(&Cell::get_value));

std::function接受几乎所有可调用的东西(自由函数,成员函数,静态函数,函数对象),并将其包装在定义一个operator()的对象中,该对象将调用转发给包装的可调用对象。
对于不太清楚std::function和可调用对象的读者可以参考我的另外一篇文章可调用对象

次优的解决方案 lambda

一种简单的方法就是将成员函数包装在lambda

std::transform(begin(inputs), end(inputs), back_inserter(outputs),
               [](const Cell& input) { return input.get_value(); });

这里说lambda表达式的实现确实简单有效,而且正确,个人认为这么去做是没有任何问题的,说它是次优解是因为引入了一个新的变量。

最适合的工具 std::mem_fn

针对本例,最适合的工具就是std::mem_fn了。它是C++2.0之后引入的,可以取代std::mem_funstd::mem_fun_ref

std::transform(begin(inputs), end(inputs), back_inserter(outputs), std::mem_fn(&Cell::get_value));

上述的写法看起来跟开篇将到的Java的例子就很类似了。
简单介绍一下std::mem_fnstd::mem_funstd::mem_fun_ref

  • 引入时间

std::mem_fn:C++11以后
std::mem_funstd::mem_fun_ref:C++98标准

  • 区别

std::mem_fun是指针版的把成员函数转换成函数对象。
std::mem_fun_ref是引用版的把成员函数转换成函数对象。
std::mem_fn则是无论指针还是引用都可以把成员函数转换为函数对象。

  • 头文件

#include <functional>

使用 range

C++20引入了range库的概念,我们看看怎么用

auto outputs = inputs | ranges::view::transform(&Cell::get_value); // OK

不过遗憾的是,目前主流的C++编译器还不支持这种写法。不过读者可以引入range-v3,用法非常简单,上面的代码不用修改,只要将range-v3的头文件引入到工程中去,有兴趣的读者可以自行去研究range-v3这个库。

后记

对于range有兴趣的读者可以参考这个网站ranges


已注销
26 声望1 粉丝