C++ 11 新特性 std:function 用法及简单实现
ztj100 2025-01-14 19:12 28 浏览 0 评论
std::function是从C++11开始支持的特性,它起什么作用?又有什么好处呢?它底层是怎么实现的呢?本文主要探讨这几个点。
先从它的用法开始,掌握了它的用法才好理解底层实现原理。
一、std::function 用法
了解std::function 之前 ,先来回忆C语言中的函数指针的用法,代码如下:
#include<iostream>
typedef int (*func)();
int print1(){
printf("hello, print1 \n");
return 0;
}
int print2(){
printf("hello, print2 \n");
return 0;
}
int main(int argc, char * argv[]){
func fp = print1;
fp();
fp = print2;
fp();
return 0;
}
上面代码中定义了一个函数指针func,它可以指向无输入参数,返回值为整型的函数。因此在main函数中,我们可以用fp(这是func类型的指针)分别指向print1和print2并调用它们。
其运行结果如下:
hello, print1
hello, print2
在C/C++中函数指针作为一种回调机制被广泛使用,但是函数指针在C++面向对象编程中有些不足,比如无法捕捉上下文。举个例子,使用对象的非静态成员函数作为函数指针就无法做到。
在C++ 中除了可以使用和C 一样使用函数指针指向一个函数外还可以通过std::function 来指向函数,与函数指针不同的是 std:function 更加强大。类模版std::function是一种通用、多态的函数封装。std::function的实例可以对任何可以调用的目标实体进行存储、复制、和调用操作,这些目标实体包括普通函数、Lambda表达式、函数指针、以及其它函数对象等。std::function对象是对C++中现有的可调用实体的一种类型安全的包裹(我们知道像函数指针这类可调用实体,是类型不安全的)。通常std::function是一个函数对象类,它包装其它任意的函数对象,被包装的函数对象具有类型为T1, …,TN的N个参数,并且返回一个可转换到R类型的值。std::function使用 模板转换构造函数接收被包装的函数对象;特别是,闭包类型可以隐式地转换为std::function。它可以接收:
- 函数
- 函数指针
- 类成员函数指针
- 任意类型的函数对象(例如:定义了operator()操作符重载的类型)
下面我们来看一下如何在C++中使用std::function实现指向不同的函数吧。
1.1 包装普通函数和静态函数
1.1.1 非模板类型代码示例
#include <iostream>
#include <functional>
#include <memory>
using namespace std;
int Add(int a, int b)
{
std::cout << "普通函数被调用,结果为:" << a + b << std::endl;
return a + b;
}
static int StaticAdd(int a, int b)
{
std::cout << "静态函数被调用,结果为:" << a + b << std::endl;
return a + b;
}
int main()
{
// 1 普通函数
std::function<int(int, int)> addFunc1 = Add;
addFunc1(1, 2);
// 2 普通函数指针
std::function<int(int, int)> addFunc2 = &Add;
addFunc2(3, 4);
// 3 静态函数
std::function<int(int, int)> staticAddFunc1 = StaticAdd;
staticAddFunc1(5, 6);
// 4 静态函数指针
std::function<int(int, int)> staticAddFunc2 = &StaticAdd;
staticAddFunc2(7, 8);
getchar();
return 0;
}
1.2 模板类型,代码示例:
#include <iostream>
#include <functional>
#include <memory>
using namespace std;
template <class T>
T Add(T a, T b)
{
std::cout << "普通模板函数被调用,结果为:" << a + b << std::endl;
return a + b;
}
template <class T>
static T StaticAdd(T a, T b)
{
std::cout << "静态模板函数被调用,结果为:" << a + b << std::endl;
return a + b;
}
int main()
{
// 1 普通函数
std::function<int(int, int)> addFunc1 = Add<int>;
addFunc1(1, 2);
// 2 普通函数指针
std::function<int(int, int)> addFunc2 = &Add<int>;
addFunc2(3, 4);
// 3 静态函数
std::function<int(int, int)> staticAddFunc1 = StaticAdd<int>;
staticAddFunc1(5, 6);
// 4 静态函数指针
std::function<int(int, int)> staticAddFunc2 = &StaticAdd<int>;
staticAddFunc2(7, 8);
getchar();
return 0;
}
1.2 包装类成员函数和类静态函数
1.2.1 非模板类型 代码示例
#include <iostream>
#include <functional>
#include <memory>
using namespace std;
class A
{
public:
typedef std::shared_ptr<A> ptr;
A(){
};
virtual~A()
{
};
public:
int A_Add_Public(int a, int b)
{
std::cout << "类公有成员函数被调用,结果为:" << a + b << std::endl;
return a + b;
}
static int A_Add_Static(int a, int b)
{
std::cout << "类静态函数被调用,结果为:" << a + b << std::endl;
return a + b;
}
public:
int m_Value = 20;
};
int main()
{
// 1 类公有成员函数
A m_a;
std::function<int(int, int)> addFunc1 = std::bind(&A::A_Add_Public, &m_a, std::placeholders::_1, std::placeholders::_2);
addFunc1(1, 2);
// 2 类静态函数
std::function<int(int, int)> addFunc2 = &A::A_Add_Static;
addFunc2(1, 2);
getchar();
return 0;
}
1.2.2 模板类型 代码示例
#include <iostream>
#include <functional>
#include <memory>
using namespace std;
class A
{
public:
typedef std::shared_ptr<A> ptr;
A()
{
};
virtual~A()
{
};
public:
template <class T>
T A_Add_Public(T a, T b)
{
std::cout << "类公有模板成员函数被调用,结果为:" << a + b << std::endl;
return a + b;
}
template <class T>
static T A_Add_Static(T a, T b)
{
std::cout << "类静态模板函数被调用,结果为:" << a + b << std::endl;
return a + b;
}
public:
int m_Value = 20;
};
int main()
{
// 1 类公有成员函数
A m_a;
std::function<int(int, int)> addFunc1 = std::bind(&A::A_Add_Public<int>, &m_a, std::placeholders::_1, std::placeholders::_2);
addFunc1(1, 2);
// 2 类静态函数
std::function<int(int, int)> addFunc2 = &A::A_Add_Static<int>;
addFunc2(1, 2);
getchar();
return 0;
}
1.3 包装Lambda表达式 代码示例
#include <iostream>
#include <functional>
#include <memory>
using namespace std;
int main()
{
// 1 lambda表达式
std::function<int(int, int)> addFunc1 = [](int a, int b) ->int {
std::cout << "lambda表达式被调用,结果为:" << a + b << std::endl;
return a + b;
};
addFunc1(1, 2);
getchar();
return 0;
}
1.4 包装类重载操作符()函数
1.4.1 非模板类型代码示例
#include <iostream>
#include <functional>
#include <memory>
using namespace std;
class A
{
public:
typedef std::shared_ptr<A> ptr;
A()
{
};
virtual~A()
{
};
public:
int operator()(int a, int b)
{
std::cout << "类重载操作符()函数被调用,结果为:" << a + b << std::endl;
return a + b;
}
public:
int m_Value = 20;
};
int main()
{
// 1 类重载操作符()函数
std::function<int(int, int)> addFunc1 = A();
addFunc1(1, 2);
getchar();
return 0;
}
1.4.2 模板类型代码示例
#include <iostream>
#include <functional>
#include <memory>
using namespace std;
template <class T>
class A
{
public:
typedef std::shared_ptr<A> ptr;
A()
{
};
virtual~A()
{
};
public:
T operator()(T a, T b)
{
std::cout << "类重载操作符()模板函数被调用,结果为:" << a + b << std::endl;
return a + b;
}
public:
int m_Value = 20;
};
int main()
{
// 1 类重载操作符()函数
std::function<int(int, int)> addFunc1 = A<int>();
addFunc1(1, 2);
getchar();
return 0;
}
1.5 包装类共有成员变量
代码示例:
#include <iostream>
#include <functional>
#include <memory>
using namespace std;
class A
{
public:
typedef std::shared_ptr<A> ptr;
A()
{
};
virtual~A()
{
};
public:
int Add()
{
return m_Value;
}
public:
int m_Value = 20;
};
int main()
{
// 1 类共有成员变量
std::function<int(A&)> A_Value = &A::m_Value;
A m_a;
std::cout << "m_a对象的成员m_Value的值为:" << A_Value(m_a) << std::endl;
getchar();
return 0;
}
看完上述用法之后感觉这玩意功能很强大,下面来深入研究它的底层实现原理。
二、std:function 简单实现
从实现上来说,有两种办法可以实现std::function:一种是通过类的多态,即通过虚表来达到多态;另一种方法是通过C语言的函数指针来实现。这种方式就是定义各种形式的函数指针,实现起来较简单,但是无法做到通用,也不符合C++ 中多态的特性。本文介绍通过类模板+函数指针的方式来实现function。
现在我们由浅入深的来分解一下function。通过观察我们可以发现function是一个包装类,它可以接收普通函数、函数类对象(也就是实现了()操作符的类对象)等。它是如何做到的呢?最简单的方式就是通过类模板。
template<typename Ty, typename A1>
class myFunction<Ty(A1)> {
...
public:
Ty operator()(A1 arg0){
return ...;
}
};
模板中 Ty 代表返回类型,A1 代表入参。
为了达到上述类模板以能够指向函数(暂且不论成员函数、静态函数)很显然还需要一个函数指针来指向可调用的对象。我们改造上述这个类模板如下:
template<typename Ty, typename A1>
class MyFunction3<Ty(A1)> {
public:
typedef Ty(*pFunction)(A1);//定义一个函数指针,指针指向的函数返回类型是Ty,有1个函数参数
MyFunction3<Ty(A1)>(pFunction _pFunction) : _function(_pFunction) {
}
Ty operator()(A1 arg1) {
return (*_function)(arg1);
}
private:
pFunction _function;
};
测试代码
void showMes(string mes) {
cout << "showMes(string mes)=" << mes << endl;
}
int main() {
MyFunction3<void(string)> f2(showMes);
f2("AAAA");
return 0;
}
运行结果:
面代码我们实现了两个模板的部分特例化
class MyFunction3<Ty(A1)> 一个函数参数的
class MyFunction3<Ty(A1,A2)> 两个函数参数的
所以问题来了...三个参数,四个参数,五个参数等若干个参数的怎么办?可以使用C++11 可变参数类型, 具体如下
#include <iostream>
#include <string>
using namespace std;
template<typename T>
class MyFunction4 {
};
template<typename R , typename... A >
class MyFunction4<R(A...)> {
public:
typedef R(*PFUNCTION)(A...);
MyFunction4<R(A...)>(PFUNCTION _p) : function(_p) {}
R operator()(A... arg) {
return (*function)(arg...);
}
private:
PFUNCTION function;
};
void showMes1(string mes) {
cout << "showMes(string mes)=" << mes << endl;
}
int sum11(int x, int y) {
cout << "sum11 " << (x + y) << endl;
return x + y;
}
int sum21(int x, int y) {
cout << "sum21 " << (x + y) << endl;
return x + y;
}
int main() {
MyFunction4<int(int, int)> f1(sum11);
f1(20, 30);
MyFunction4<void(string)> f2(showMes1);
f2("AAAA");
system("pause");
return 0;
}
function 封装 类成员方法 类模板如下:
//function 封装 类成员方法
template <typename R,typename Ty, typename A1, typename A2>
class MyFunction4<R(Ty::*)(A1,A2)>{
public:
typedef R(Ty::* functionptr)(A1,A2);
MyFunction4(functionptr ptr):ptr_(ptr){}
R operator()(Ty * pp,A1 a1,A2 a2){
// ptr_ 是指向类成员方法的函数指针, pp 是对象指针,通过对象指针调用成员方法
return (pp->*ptr_)(a1,a2);
}
private:
functionptr ptr_;
};
int main(){
Test t;
MyFunction4<int(Test::*)(int,int)> function(&Test::sum);
int res = function(&t , 10 , 20);
std::cout<<res<<std::endl;
return 1;
}
三、std::function 底层原码(部分)
template <class T>
class function; // 只声明,不定义
template <class R, class... ArgTypes>
class function<R(ArgTypes...)> {
public:
using result_type = R;
// 构造/复制/销毁
function() noexcept;
function(nullptr_t) noexcept;
function(const function&);
function(function&&) noexcept;
template <class F>
function(F);
function& operator=(const function&);
function& operator=(function&&);
function& operator=(nullptr_t) noexcept;
template <class F>
function& operator=(F&&);
template <class F>
function& operator=(reference_wrapper<F>) noexcept;
~function();
// function 修改器
void swap(function&) noexcept;
// function 容量
explicit operator bool() const noexcept;
// function 调用
R operator()(ArgTypes...) const;
// function 目标访问
const type_info& target_type() const noexcept;
template <class T>
T* target() noexcept;
template <class T>
const T* target() const noexcept;
};
template <class R, class... ArgTypes>
function(R (*)(ArgTypes...)) -> function<R(ArgTypes...)>;
template <class F>
function(F) -> function</* see description */>;
// 空指针比较函数
template <class R, class... ArgTypes>
bool operator==(const function<R(ArgTypes...)>&, nullptr_t) noexcept;
// 特化的算法
template <class R, class... ArgTypes>
void swap(function<R(ArgTypes...)>&, function<R(ArgTypes...)>&) noexcept;
相关推荐
- 30天学会Python编程:16. Python常用标准库使用教程
-
16.1collections模块16.1.1高级数据结构16.1.2示例...
- 强烈推荐!Python 这个宝藏库 re 正则匹配
-
Python的re模块(RegularExpression正则表达式)提供各种正则表达式的匹配操作。...
- Python爬虫中正则表达式的用法,只讲如何应用,不讲原理
-
Python爬虫:正则的用法(非原理)。大家好,这节课给大家讲正则的实际用法,不讲原理,通俗易懂的讲如何用正则抓取内容。·导入re库,这里是需要从html这段字符串中提取出中间的那几个文字。实例一个对...
- Python数据分析实战-正则提取文本的URL网址和邮箱(源码和效果)
-
实现功能:Python数据分析实战-利用正则表达式提取文本中的URL网址和邮箱...
- python爬虫教程之爬取当当网 Top 500 本五星好评书籍
-
我们使用requests和re来写一个爬虫作为一个爱看书的你(说的跟真的似的)怎么能发现好书呢?所以我们爬取当当网的前500本好五星评书籍怎么样?ok接下来就是学习python的正确姿...
- 深入理解re模块:Python中的正则表达式神器解析
-
在Python中,"re"是一个强大的模块,用于处理正则表达式(regularexpressions)。正则表达式是一种强大的文本模式匹配工具,用于在字符串中查找、替换或提取特定模式...
- 如何使用正则表达式和 Python 匹配不以模式开头的字符串
-
需要在Python中使用正则表达式来匹配不以给定模式开头的字符串吗?如果是这样,你可以使用下面的语法来查找所有的字符串,除了那些不以https开始的字符串。r"^(?!https).*&...
- 先Mark后用!8分钟读懂 Python 性能优化
-
从本文总结了Python开发时,遇到的性能优化问题的定位和解决。概述:性能优化的原则——优化需要优化的部分。性能优化的一般步骤:首先,让你的程序跑起来结果一切正常。然后,运行这个结果正常的代码,看看它...
- Python“三步”即可爬取,毋庸置疑
-
声明:本实例仅供学习,切忌遵守robots协议,请不要使用多线程等方式频繁访问网站。#第一步导入模块importreimportrequests#第二步获取你想爬取的网页地址,发送请求,获取网页内...
- 简单学Python——re库(正则表达式)2(split、findall、和sub)
-
1、split():分割字符串,返回列表语法:re.split('分隔符','目标字符串')例如:importrere.split(',','...
- Lavazza拉瓦萨再度牵手上海大师赛
-
阅读此文前,麻烦您点击一下“关注”,方便您进行讨论和分享。Lavazza拉瓦萨再度牵手上海大师赛标题:2024上海大师赛:网球与咖啡的浪漫邂逅在2024年的上海劳力士大师赛上,拉瓦萨咖啡再次成为官...
- ArkUI-X构建Android平台AAR及使用
-
本教程主要讲述如何利用ArkUI-XSDK完成AndroidAAR开发,实现基于ArkTS的声明式开发范式在android平台显示。包括:1.跨平台Library工程开发介绍...
- Deepseek写歌详细教程(怎样用deepseek写歌功能)
-
以下为结合DeepSeek及相关工具实现AI写歌的详细教程,涵盖作词、作曲、演唱全流程:一、核心流程三步法1.AI生成歌词-打开DeepSeek(网页/APP/API),使用结构化提示词生成歌词:...
- “AI说唱解说影视”走红,“零基础入行”靠谱吗?本报记者实测
-
“手里翻找冻鱼,精心的布局;老漠却不言语,脸上带笑意……”《狂飙》剧情被写成歌词,再配上“科目三”背景音乐的演唱,这段1分钟30秒的视频受到了无数网友的点赞。最近一段时间随着AI技术的发展,说唱解说影...
- AI音乐制作神器揭秘!3款工具让你秒变高手
-
在音乐创作的领域里,每个人都有一颗想要成为大师的心。但是面对复杂的乐理知识和繁复的制作过程,许多人的热情被一点点消磨。...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 30天学会Python编程:16. Python常用标准库使用教程
- 强烈推荐!Python 这个宝藏库 re 正则匹配
- Python爬虫中正则表达式的用法,只讲如何应用,不讲原理
- Python数据分析实战-正则提取文本的URL网址和邮箱(源码和效果)
- python爬虫教程之爬取当当网 Top 500 本五星好评书籍
- 深入理解re模块:Python中的正则表达式神器解析
- 如何使用正则表达式和 Python 匹配不以模式开头的字符串
- 先Mark后用!8分钟读懂 Python 性能优化
- Python“三步”即可爬取,毋庸置疑
- 简单学Python——re库(正则表达式)2(split、findall、和sub)
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)