C++可调用对象callableobject深入分析

2022-11-13 15:11:49 分析 对象 调用

本作者一致的观点就是 在任何语言执行的时候先去思考汇编层面能不能做到 如果能做到 那么高级语言才能做到 无论你推出什么新特性 用户态汇编你都是绕不开的 比如你要调用函数 那么你必须要使用call指令 那么就必须要有函数地址 接下来我们来详细说说为什么c++11要推出这个新概念 以及他解决了什么问题 还有如何使用它

Tips:c++的设计哲学是你必须时刻清楚你自己在干什么 stl内部并不会给你执行任何的安全检查 程序直接崩溃也是完全有可能的 功力不够 就不要玩花的

为什么需要他

在c++11还没有推出callable object的时候 那时候如果你想要把普通函数当做参数一样传递那么你就只能使用函数指针 如下段代码所示

我就不再演示如果你有函数指针如何调用函数了 空指针发生调用异常啥的也是你自己的问题那非常简单了 不需要过多描述

注意 FuncP是一种类型 而非一个指针

#include <iOStream>
void test(int i)
{
	return;
}
typedef void(*FuncP)(int i);
void recevier(void(*)(int))
{
	std::cout << "recevier working!" << std::endl;
}
int main()
{
	recevier(&test);
	FuncP ding = test;
	recevier(&test);
	void(*FuncP1)(int) = test;
	recevier(FuncP1);
}

运行结果截图:

而如果你想把类的成员函数传递 那更是麻烦 因为我们知道类的成员函数在调用的时候是需要传递this指针的 那么我们来看一看如何传递类的成员函数 如下段代码所示

#include <iostream>
class testclass
{
public:
	void MemberFunc(int i)
	{
		std::cout << "MemberFunc" << std::endl;
	}
};
typedef void(testclass::* CFuncP)(int);
void recevier(void(testclass::* memberFunc)(int))
{
	testclass p;
	(p.*memberFunc)(1);
	std::cout << "recevier working!" << std::endl;
}
void recevier(void(*)(int))
{
	std::cout << "recevier working!" << std::endl;
}
int main()
{
	recevier(&testclass::MemberFunc);
	CFuncP ding = &testclass::MemberFunc;
	recevier(ding);
	void(testclass:: * testF)(int) = &testclass::MemberFunc;
	recevier(testF);
}

运行结果如下图

可以看到啊 虽然咱们用普通的成员函数指针和普通的函数指针似乎已经可以解决绝大部分问题 那为什么又要推出一个callable object呢? 关键在于这种代码实现起来 其实可读性很差 而且不是那么好理解 如果有一种统一的方式来让我们可以把函数当做参数传递 并且能直接调用就好了 那么c++11便推出了callable object

他究竟是啥

那么究竟什么叫做callable object呢 顾名思义 就是可以被调用的对象 看如下一个最简单的例子

#include <iostream>
class testclass
{
public:
	void MemberFunc(int i)
	{
		std::cout << "MemberFunc" << std::endl;
	}
	void operator()()
	{
		std::cout << "callable object working" << std::endl;
	}
};
int main()
{
	testclass ding;
	ding();
}

当一个对象 重载了() 运算符的时候 我们就把他看做 一个最简单的callable object 因为他这个对象可以像函数一样被调用是吧 没有什么问题 那么接下来我们就来看看stl提供的 能让你快速生成callable object的组件之std::bind

他怎样被使用呢

我们先来看一个最简单的例子 基于std::bind

void test(int i, int j, int k)
{
	std::cout << "test working!" << std::endl;
	std::cout << i << std::endl;
	std::cout << j << std::endl;
	std::cout << k << std::endl;
}
int main()
{
	std::bind(&test, 1, 2, 3)();
}

如上图所示 我们使用std::bind来创建了一个callable object并且在创建这个callable object的时候就已经把他三个参数传递好了 这点非常重要 这会影响到接下来的占位符的讲解 然后马上使用()来调用了他 运行结果如下图所示

相比较于普通的函数指针 这样的方式是不是更简单明了了呢? 当然这远远不是他的全部 接下来我们来看看使用占位符的时候 该如何使用它 代码如下图所示

void test(int i, int j, int k)
{
	std::cout << "test working!" << std::endl;
	std::cout << i << std::endl;
	std::cout << j << std::endl;
	std::cout << k << std::endl;
}
int main()
{
	std::bind(&test, 1,std::placeholders::_1,std::placeholders::_2)(2,3);
}

可以看到上图 我们在创建callable object的时候 并没有去传递全部的参数 而是使用了占位符 然后在真正调用的时候才去传递了2和3两个参数 运行结果如下图所示

下面让我们升华到成员函数好嘛? 不过多讲解 咱们直接看代码

#include <iostream>
#include <functional>
class testclass
{
public:
	void MemberFunc(int i,int j,int k)
	{
		std::cout << "MemberFunc" << std::endl;
		std::cout << i << std::endl;
		std::cout << j << std::endl;
		std::cout << k << std::endl;
	}
	void operator()()
	{
		std::cout << "callable object working" << std::endl;
	}
};
int main()
{
	std::bind(&testclass::MemberFunc, testclass(), 1, std::placeholders::_1, std::placeholders::_2)(2, 3);
}

成员函数的调用时需要this指针的 我相信大家都知道 那么这就是为什么我在std::bind的第二个参数创建了一个临时对象 用来传递this指针 可以看到相比较于普通的成员函数指针 这种方式明显要方便的多 注意啊 使用std::bind来创建成员函数的callable object的时候 第二个参数必须是对象或者某个对象的this指针 不然直接会G的

乱玩导致的G 你自己负责 如下图所示

当然 像下图这样先使用占位符将传入对象暂定 然后在参数中去传递对象或this指针也是可以的

std::bind
(&testclass::MemberFunc,std::placeholders::_1, 1, std::placeholders::_2,
std::placeholders::_3)(testclass(),2, 3);

这个时候你就要问了 OK 这样确实很方便 没错 但是你说了这么久也没见你把它当做参数来传递啊 下面就来介绍std::function 他不仅能当做参数来传递 并且还可以保存callable object 属实是非常方便 老样子 还是从最简单的例子开始 代码如下图

#include <iostream>
#include <functional>
class testclass
{
public:
	int ding{};
	void MemberFunc(int i,int j,int k)
	{
		std::cout << "MemberFunc" << std::endl;
		std::cout << i << std::endl;
		std::cout << j << std::endl;
		std::cout << k << std::endl;
		std::cout << ding << std::endl;
	}
	void operator()()
	{
		std::cout << "callable object working" << std::endl;
	}
};
int main()
{
	std::function<void(int,int,int)>MemberCall=std::bind(&testclass::MemberFunc,testclass(), 1, std::placeholders::_1, std::placeholders::_2);
	MemberCall(1,2,NULL);
	std::function<void(testclass&, int, int, int) > MemberCall2=&testclass::MemberFunc;
	testclass C;
	MemberCall2(C, 1, 1, 1);
}

如上图所示 因为我已经放置了一个占位符 所以呢 第三个参数无论你填什么 都是没用的 只会取前两个参数 调用结果如下所示:

我们再来试试将他作为参数传递 很简单咯 玩起来

void test(std::function<void(int, int, int)>& Func, int c)
{
	Func(1, 2, 3);
	std::cout << c<<std::endl;
}
int main()
{
	std::function<void(int,int,int)>MemberCall=std::bind(&testclass::MemberFunc,testclass(), 1, std::placeholders::_1, std::placeholders::_2);
	MemberCall(1,2,NULL);
	std::function<void(testclass&, int, int, int) > MemberCall2=&testclass::MemberFunc;
	testclass C;
	MemberCall2(C, 1, 1, 1);
	test(MemberCall, 3);
}

运行结果如下:

看看 有了std::function和std::bind 函数就像对象一样可以被传递保存和调用 是不是很方便呢

Tips:既然他是一个对象了 那任何对对象的操作对于他都是允许的 比如塞入队列啥的 可以任意交互 其他东西我就不演示了 对于对象的各种操作是基本知识

最后让我们来看看他跟lambda之间的化学反应 代码如下:

#include <iostream>
#include <functional>
class testclass
{
public:
	int ding{};
	void MemberFunc(int i,int j,int k)
	{
		std::cout << "MemberFunc" << std::endl;
		std::cout << i << std::endl;
		std::cout << j << std::endl;
		std::cout << k << std::endl;
		std::cout << ding << std::endl;
	}
	void operator()()
	{
		std::cout << "callable object working" << std::endl;
	}
};
void test(std::function<void(int, int, int)>& Func, int c)
{
	Func(1, 2, 3);
	std::cout << c<<std::endl;
}
int main()
{
	std::function<void(int,int,int)>MemberCall=std::bind(&testclass::MemberFunc,testclass(), 1, std::placeholders::_1, std::placeholders::_2);
	std::function<void(testclass&&,int, int, int)>MemberCall1 = std::bind(&testclass::MemberFunc, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,std::placeholders::_4);
	MemberCall1(testclass(),1, 2, 3);
	MemberCall(1,2,3);
	std::function<void(testclass&, int, int, int) > MemberCall2=&testclass::MemberFunc;
	testclass C;
	MemberCall2(C, 1, 1, 1);
	test(MemberCall, 3);
	std::function<bool(int,int,int)> play=std::bind([&C](testclass& c, int i, int j, int k)->bool {
		c.MemberFunc(i, j, k);
		std::cout << "lambda working!" << std::endl;
		}, C, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
	play(1, 2, 3);
}

运行结果如下

相信细心的小伙伴已经发现我多加了一个Membercall1 并且使用了右值引用 关于这个 我们下一篇文章再说好嘛? 本期callable object讲解结束 认真看完相信你能学到很多东西

到此这篇关于C++可调用对象callable object深入分析的文章就介绍到这了,更多相关C++ callable object内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

相关文章