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 inputs = {Cell(3), Cell(10), Cell(113)};
std::vector 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(&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_fun
和std::mem_fun_ref
。
std::transform(begin(inputs), end(inputs), back_inserter(outputs), std::mem_fn(&Cell::get_value));
上述的写法看起来跟开篇将到的Java的例子就很类似了。
简单介绍一下std::mem_fn
、std::mem_fun
和std::mem_fun_ref
- 引入时间
std::mem_fn
:C++11以后std::mem_fun
和std::mem_fun_ref
:C++98标准
- 区别
std::mem_fun
是指针版的把成员函数转换成函数对象。std::mem_fun_ref
是引用版的把成员函数转换成函数对象。std::mem_fn
则是无论指针还是引用都可以把成员函数转换为函数对象。
- 头文件
#include
使用 range
C++20引入了range库的概念,我们看看怎么用
auto outputs = inputs | ranges::view::transform(&Cell::get_value); // OK
不过遗憾的是,目前主流的C++
编译器还不支持这种写法。不过读者可以引入range-v3
,用法非常简单,上面的代码不用修改,只要将range-v3
的头文件引入到工程中去,有兴趣的读者可以自行去研究range-v3
这个库。
后记
对于range
有兴趣的读者可以参考这个网站ranges