C++可调用对象callable object深入分析

目录
  • 为什么需要他
  • 他究竟是啥
  • 他怎样被使用呢

本作者一致的观点就是 在任何语言执行的时候先去思考汇编层面能不能做到 如果能做到 那么高级语言才能做到 无论你推出什么新特性 用户态汇编你都是绕不开的 比如你要调用函数 那么你必须要使用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内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++解析obj模型文件方法介绍

    目录 一.前言 二.中间文件 三.使用 四.完整代码 一.前言 tinyobjloader地址: 传送门 而tinyobjloader库只有一个头文件,可以很方便的读取obj文件.支持材质,不过不支持骨骼动画,vulkan官方教程便是使用的它.不过没有骨骼动画还是有很大的局限性,这里只是分享一下怎么读取材质和拆分网格. 二.中间文件 我抽象了一个ModelObject类表示模型数据,而一个ModelObject包含多个Sub模型,每个Sub模型使用同一材质(有的人称为图元Primitive或Dr

  • C++可调用对象callable object深入分析

    目录 为什么需要他 他究竟是啥 他怎样被使用呢 本作者一致的观点就是 在任何语言执行的时候先去思考汇编层面能不能做到 如果能做到 那么高级语言才能做到 无论你推出什么新特性 用户态汇编你都是绕不开的 比如你要调用函数 那么你必须要使用call指令 那么就必须要有函数地址 接下来我们来详细说说为什么c++11要推出这个新概念 以及他解决了什么问题 还有如何使用它 Tips:c++的设计哲学是你必须时刻清楚你自己在干什么 stl内部并不会给你执行任何的安全检查 程序直接崩溃也是完全有可能的 功力不够

  • JavaScript词法作用域与调用对象深入理解

    关于 Javascript 的函数作用域.调用对象和闭包之间的关系很微妙,关于它们的文章已经有很多,但不知道为什么很多新手都难以理解.我就尝试用比较通俗的语言来表达我自己的理解吧. 作用域 Scope Javascript 中的函数属于词法作用域,也就是说函数在它被定义时的作用域中运行而不是在被执行时的作用域内运行.这是犀牛书上的说法.但"定义时"和"执行(被调用)时"这两个东西有些人搞不清楚.简单来说,一个函数A在"定义时"就是 functio

  • Python判断对象是否为文件对象(file object)的三种方法示例

    文件操作是开发中经常遇到的场景,那么如何判断一个对象是文件对象呢?下面我们总结了3种常见的方法. 方法1:比较类型 第一种方法,就是判断对象的type是否为file >>> fp = open(r"/tmp/pythontab.com") >>> type(fp) <type 'file'> >>> type(fp) == file True 注意:该方法对于从file继承而来的子类不适用, 看下面的实例 class f

  • Java的反射机制---动态调用对象的简单方法

    唉!我还真是在面试中学习新东东啊,一个公司刚刚给了个测试,不过我很奇怪的是为什么web developer的职位居然考java的反射机制题,不过学习研究一下反射机制对我来说是件好事啦! 先说说什么是java反射机制吧,在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法:这 种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制.主要功能:在运行时判断任意一个对象所属的类:在运行时构造任意一个类的对 象:在运行时判断任意一个

  • JavaScript 复制对象与Object.assign方法无法实现深复制

    在JavaScript这门语言中,数据类型分为两大类:基本数据类型和复杂数据类型.基本数据类型包括Number.Boolean.String.Null.String.Symbol(ES6 新增),而复杂数据类型包括Object,而所有其他引用类型(Array.Date.RegExp.Function.基本包装类型(Boolean.String.Number).Math等)都是Object类型的实例对象,因此都可以继承Object原型对象的一些属性和方法. 而对于基本数据类型来说,复制一个变量值,

  • Python3 实现减少可调用对象的参数个数

    问题 一个被其他python代码使用的callable对象,可能是一个回调函数或者是一个处理器,由于其参数太多,导致调用时出错. 解决方案 如果需要减少某个函数的参数个数,可以使用functools.partial() . partial() 函数允许给一个或多个参数设置固定的值,减少接下来被调用时的参数个数. 假设一个函数有很多参数: def func(a, b, c, d): print(a, b, c, d) 使用 partial() 函数来固定某些参数值: from functools

  • 了解java中对象基础Object类

    目录 一.Object简述 1.显式扩展 2.引用与对象 二.基础方法 1.getClass 2.toString 3.equals与hashCode 4.thread相关 5.clone 6.finalize 三.生命周期 1.作用域 2.垃圾回收机制 四.源代码地址 一.Object简述 源码注释:Object类是所有类层级关系的Root节点,作为所有类的超类,包括数组也实现了该类的方法,注意这里说的很明确,指类层面. 所以在Java中有一句常说的话,一切皆对象,这话并不离谱. 1.显式扩展

  • 深入理解JavaScript中的对象复制(Object Clone)

    JavaScript中并没有直接提供对象复制(Object Clone)的方法.因此下面的代码中改变对象b的时候,也就改变了对象a. a = {k1:1, k2:2, k3:3}; b = a; b.k2 = 4; 如果只想改变b而保持a不变,就需要对对象a进行复制. 用jQuery进行对象复制 在可以使用jQuery的情况下,jQuery自带的extend方法可以用来实现对象的复制. a = {k1:1, k2:2, k3:3}; b = {}; $.extend(b,a); 自定义clone

  • Javascript 对象(object)合并操作实例分析

    本文实例讲述了Javascript 对象(object)合并操作.分享给大家供大家参考,具体如下: 对象的合并 需求:设有对象 o1 ,o2,需要得到对象 o3 var o1 = { a:'a' }, o2 = { b:'b' }; // 则 var o3 = { a:'a', b:'b' } 方法1:使用JQuery的extend方法 **方法定义**:jQuery.extend([deep], target, object1, [objectN]) > 用一个或多个其他对象来扩展一个对象,返

  • django小技巧之html模板中调用对象属性或对象的方法

    环境:依赖最初test2数据库 python3版本            多python版本环境 进入,python3虚拟环境,新建项目test4: ]# cd py3/django-test1/ ]# django-admin startproject test4 创建应用bookshop: ]# cd test4 ]# python manage.py startapp bookshop 修改settings.py主配置文件: ]# vim test4/settings.py ... #数据

随机推荐