颜林林的个人网站

现代C++学习笔记(7):函数对象

2022-02-12 23:49

Image (题图来自网络并做适当修改)

1

早在现代C++出现之前,即在2011年发布C++11标准之前,人们就早已不满经典的C++语言种种局限,而做了多种不同的尝试,比如发明了Java语言和C#语言。

Java语言从一开始,就是模仿C/C++的语法进行的设计,继承了面向对象的核心理念,同时舍弃了诸如指针等难以驾驭的“危险源”,试图构造一门全新的“胜过C++”的语言。

而C#语言则是作为.NET体系的第一个实现及其官方支持语言,诞生于微软帝国。要是探究相关历史,有两本经典老书很值得一提:《COM本质论》和《.NET本质论》。前者说“COM是一个更好的C++”,后者说“.NET是一个更好的COM”。

COM作为Windows的功能组件架构,在.NET普及之前曾经大行其道,它尝试对“过于简陋”的C++对象进行改良,在诸如接口定义、生存期管理、跨语言使用、跨进程甚至跨计算机调用等方面,做出了一系列改进。而.NET则更进一步,从运行时框架角度对上述特性做了重新设计和实现,甚至发明了C#这样一门新语言来支持它,使得它使用起来像C++,功能却远胜于C++。

然而,纵使如此,C++语言却从未被这些语言取代,各语言伴着自己的特性和生态圈,独立地发展着。有趣的是,随着二十多年过去,Java和C#,这两种语言,从设计理念,到实现细节,都彼此越来越像。而比较重要的,它们都是“纯”面向对象语言,在由它们编写的程序中,“一切皆对象”。

而C++语言却并非如此。C++是个多面手,是一个“多语言联邦”,面向对象只是它的其中一面。本次笔记,将介绍一种由语法技巧的使用,人工“创造”出来的对象:函数对象。

2

直接看一个例子:

#include

class MakeDouble { public: int operator()(int x) { return 2 * x; } };

int main() { MakeDouble f; std::cout « f(42) « std::endl; // 84 } 可以看到,通过重载运算符 operator (),就能把对象实例模拟成一个函数。在上述例子中,变量“f”可以像函数一样被使用,带上小括号和参数,并返回计算结果。

3

由于这种函数对象是用 class 定义的,也就意味着我们可以在其内部加入成员变量,记录一些不同的参数。例如:

#include

class Magnifier { public: explicit Magnifier(int factor): factor_(factor) { } int operator()(int x) { return x * factor_; } private: int factor_; };

int main() { Magnifier make_double(2); Magnifier make_triple(3); std::cout « make_double(42) « std::endl; // 84 std::cout « make_triple(42) « std::endl; // 126 return 0; } 同一个类定义,通过设置不同的初始化参数,形成功能各异的“函数对象”。

4

上次笔记中提到的 std::mem_fn,将成员函数包装成为一个对象,以避免直接使用危险的函数指针。其实现正是基于上述技巧原理。

由此,我们可以尝试自己实现个简版的 mem_fn:

#include #include

class A { public: void foo(int x) { std::cout « “A::foo(” « x « “)” « std::endl; } };

class my_mem_fn { public: explicit my_mem_fn(void (A::*func)(int)): func_(func) { } void operator()(A& obj, int x) { (obj.*func_)(x); } private: void (A::*func_)(int); };

int main() { A a; a.foo(1); // A::foo(1) auto f1 = std::mem_fn(&A::foo); f1(a, 2); // A::foo(2) auto f2 = my_mem_fn(&A:foo); f2(a, 3); // A::foo(3) return 0; } 上述例子中,我们硬编码了函数类型,固定为一个int参数和无返回值。实际编程中,并不可能预见到函数的类型,于是就到模板上场了。读者可以自行继续尝试,这里暂不展开。

不过可以提示:在C++中,曾经引入过诸如 bind1st() 等,来帮助把一个函数封装成为对象,而在现代C++中,新标准及语法的引入(如可变参数模板),使得对各式各样无法预见的函数类型,能够进行有效的支持。

--- END ---

注:本文首发表于“不靠谱颜论”公众号,并同步至本站。

相关文章