C++可调用对象与function

C++语言中可调用的对象

  • 函数
  • 函数指针
  • lambda表达式
  • bind创建的对象
  • 重载了函数调用运算符的类

和其他对象一样,可调用的对象也有类型。例如,每个lambda有它自己唯一的(未命名)类类型;函数及函数指针的类型则由其返回值类型和实参类型决定,等等。

然而,两个不同类型的可调用对象却可能共享一种调用形式(call signature)。调用形式指明了调用返回的类型以及传递给调用的实参类型。一种调用形式对应一个函数类型,例如:

int(int, int)

是一个函数类型,它接受两个int、返回一个int

不同类型可能具有相同的调用形式

对于几个可调用对象共享一种调用形式的情况,有时我们会希望把它们看成具有相同的类型。例如,考虑下列不同类型的可调用对象:

//普通函数
int add(int i, int j) { return i + j; }
//lambda,其产生一个未命名的函数对象类
auto mod = [](int i, int j) { return i % j; }
//函数对象类
struct divide
{
    int operator()(int denominator, int divisor)
    {
        return denominator / divisor;
    }
}

上面这些可调用对象分别对其参数执行了不同的算数运算,尽管它们的类型各不相同,但是共享一种调用形式:

int(int, int)

我们可能希望使用这些可调用对象构建一个简单的桌面计算器。为了实现这一目的,需要定义一个函数表(function table)用于存储指向这些可调用对象的“指针”。当程序需要执行某个特定的操作时,从表中查找该调用的函数。

在C++语言中,函数表很容易通过map来实现。

//构建从运算符到函数指针的映射关系,其中函数接受两个int、返回一个int
map<string, int(*)(int, int)> binops;

我们可以按照下面的形式将add的指针添加到binops中:

//正确:add是一个指向正确类型函数的指针
binops.insert({"+", add});	//{"+", add}是一个pair

但是我们不能将mod或者divide存入binops

binops.insert({"%", mod});	//错误:mod不是一个函数指针

问题在于mod是个lambda表达式,而每个lambda有它自己的类类型,该类型与存储在binops中的值的类型不匹配。

标准库function类型

我们可以使用一个名为function的新的标准库类型解决上述问题,function定义在functional头文件中。

function是一个模板,和其他模板一样,当创建一个具体的function类型时我们必须提供额为的信息。在此例中,所谓额外的信息是指该function类型能够表示的对象的调用形式。参考其他模板,我们在一对尖括号内指定类型:

function<int(int, int)>

在这里我们声明了一个function类型,它可以表示接受两个int、返回一个int的可调用对象。因此,我们可以用这个新声明的类型表示任意一种桌面计算器用到的类型:

function<int(int, int)> f1 = add;									//函数指针
function<int(int, int)> f2 = divide();								//函数对象类的对象
function<int(int, int)> f3 = [](int i, int j) { return i * j; };	  //lambda

cout << f1(4, 2) << endl;		//6
cout << f2(4, 2) << endl;		//2
cout << f3(4, 2) << endl;		//8

使用这个function类型我们可以重新定义map

//列举了可调用对象与二元运算符对应关系的表格
//所有可调用对象都必须接受两个int、返回一个int
//其中的元素可以是函数指针、函数对象或者lambda
map<string, function<int(int, int)>> binops;

我们能把所有可调用对象,包括函数指针、lambda或者函数对象在内,都添加到这个map中:

map<string, function<int(int, int)>> binops =
{
        {"+", add},                 // 函数指针
        {"-", std::minus<int>()},   // 标准库函数对象
        {"/", divide()},            // 用户定义的函数对象
        {"*", [](int i, int j)
         { return i * j; }},        // 未命名的lambda
        {"%", mod}                  // 命名了的lambda对象
}

我们的map中包含5个元素,尽管其中的可调用对象的类型各不相同,我们仍然能够把所有这些类型都存储在同一个function<int(int, int)>类型中。

当我们索引map时将得到关联值的一个引用。如果我们索引binops,将得到function对象的引用。function类型重载了调用运算符,该运算符接受它自己的实参然后将其传递给存好的可调用对象。

binops["+"](10, 5);		//调用add(10, 5)
binops["-"](10, 5);		//调用minus<int>对象的调用运算符
binops["/"](10, 5);		//调用divide对象的调用运算符
binops["*"](10, 5);		//调用lambda函数对象
binops["%"](10, 5);		//调用lambda函数对象

我们依次调用了binops中存储的每个操作。在第一个调用中,我们获得的元素存放着一个指向add函数的指针,因此调用binops["+"](10, 5)实际上是使用该指针调用add,并10和5。在接下来的调用中,binops["-"]返回一个存放着std::minus<int>类型对象的function,我们将执行该对象的调用运算符。

重载的函数与function

我们不能(直接)将重载函数的名字存入function类型的对象中:

int add(int i, int j) { return i + j; }
Sales_data add(const Sales_data&, const Sales_data&);
map<string, function<int(int, int)>> binops;
binops.insert({"+", add});	//错误:哪个add?

解决上述二义性问题的一条途径是存储函数指针而非函数的名字:

int (*fp)(int, int) = add;	//指针所指的add是接受两个int的版本
binops.insert({"+", fp});	//正确:fp指向一个正确的add版本

同样,我们也能使用lambda来消除二义性:

//正确:使用lambda来指定我们希望使用的add版本
binops.insert({"+", [](int a, int b){return add(a, b);}});

lambda内部的函数调用传入了两个int,因此该调用只能匹配接受两个int版本的add版本,而这也正是执行lambda时真正调用的函数。

posted @   牛奶巧克力冰糕  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示