原文链接:C++11 wrapper装饰器 bind+function
前言
装饰器本身是为了更好的支持多态性,减小开发的复杂度和代码量.
c++11标准
bind函数
格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| template< class F, class... Args > bind( F&& f, Args&&... args );
int func(int a,int& b){ return a+b; } int b=2; auto func1=std::bind(func,a,std::ref(b));
第一个参数就是函数指针,如果是普通成员函数的话,第二个参数为实例对象引用
func1大致类型为: (std::_Bind, std::reference_wrapper))(int, int&)>)
不同的封装格式不同,例如使用占位符的格式会增加placeholders部分
|
按绑定函数类型分:
- 普通函数
- 普通成员函数
- 静态成员函数
按参数类型分:
- 普通参数
- 引用参数
- 占位符
特性
- const类型参数在bind里面基本上没啥意义,普通参数bind之后,对应参数的值也不会变化
- 占位符必须从_1开始递增使用
- 占位符不能替代引用参数, 引用参数能在函数内被修改,占位符不能实现这个特性
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #include<functional> #include<iostream>
using namespace std::placeholders;
int func(int a,int& b){ return a+b; }
class C{ public: C(){} static int scall(int a,int& b){ return a+b; } int call(int a,int& b) { return a+b; } };
int main(){ int a=1; int b; auto func1=std::bind(func,a,std::ref(b)); b=2; std::cout<<"1 普通函数 sum(a,ref(b))="<<func1()<<"\n"; C c; auto func2=std::bind(&C::call,&c,a,std::ref(b)); b=3; std::cout<<"2 普通成员函数 C.c.sum(a,ref(b))="<<func2()<<"\n"; auto func3=std::bind(&C::scall,a,std::ref(b)); b=4; std::cout<<"3 静态成员函数 C.sum(a,ref(b))="<<func3()<<"\n"; auto func4=std::bind(func,_1,std::ref(b)); b=5; std::cout<<"1 普通函数 sum(a,ref(b))="<<func4(-5)<<"\n"; }
|
function对象
格式
function对象是一个能够表示重载operator()的类对象,可以理解为一个实现了指定函数功能的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int func(int a,int& b){ return a+b; }
std::function<int(int,int&)> fobj=func; 使用fobj(arg1,arg2) 调用
std::function<int()> fobj_bind=std::bind(func,a,std::ref(b)); 使用fobj() 无参调用,并且参数b是引用传值
fobj_bind=std::bind(func,a,-a);
|
特性
function对象相比函数指针,主要的优势
- 简单直观 function定义更符合对象定义,而指针类型定义复杂
- 灵活 函数对象存放的函数可以替换
- 优化 函数对象编译时会自动内联调用 函数指针无法实现
- c++对function就行了优化, 使用function相比函数指针会更安全
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include<functional> #include<iostream>
using namespace std::placeholders;
int func(int a,int& b){ return a+b; }
class C{ public: C(){} static int scall(int a,int& b){ return a+b; } int call(int a,int& b) { return a+b; } };
int main(){ int a=1; int b=2; std::function<int(int,int&)> fobj=func; std::function<int()> fobj_bind=std::bind(func,a,std::ref(b)); std::cout<<"调用普通函数的function对象:"<<fobj(a,b)<<"\n"; b=3; std::cout<<"调用bind封装后函数的function对象:"<<fobj_bind()<<"\n"; fobj_bind=std::bind(func,a,-a); std::cout<<"function对象替换内部函数:"<<fobj_bind()<<"\n"; return 0; }
|
为什么需要装饰器wrapper? : 更好的支持多态
c++函数指针类型
先了解C++的函数,一般函数定义类似为:
int(*sum)(int,int)
sum为函数指针变量,对应的类型就是int(*)(int,int)
两个相同类型的函数指针变量可以相互赋值.
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include<iostream> int main(){ int(*sum)(int,int); int(*tmp)(int,int)=[](int a,int b){return a+b;}; std::cout<<"tmp(1,2)="<<tmp(1,2)<<"\n"; sum=tmp; std::cout<<"sum(1,2)="<<sum(1,2)<<"\n";
return 0; }
|
但是类的成员函数有所不同,例如类A的普通成员函数sum类型为: int(A::*)(int,int) 其中包含了A这个类.
如果想声明为普通的函数指针形式,那么需要加上static,表示静态成员函数.
没有装饰器,函数指针下实现基类调用派生类函数
现在假设一个场景,抽象基类Base有一个回调函数指针,Base定义一个interface函数.
要求派生类的实例通过基类的函数调用自身定义的函数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include<functional> #include<iostream>
class Base{ public: virtual void interface()=0; protected: void(*callback)()=nullptr; };
class DerivedB:Base{ public: DerivedB(){ callback=&DerivedB::func1; } void interface() override{ callback(); } private: static void func1(){ std::cout<<"func1"<<"\n"; } };
int main(){ DerivedB db; db.interface(); return 0; }
|
可以看到,该例中,我们要实现静态的成员函数,然后赋值基类的函数指针,再重载基类的函数.
一套下来非常复杂,完全没有减少代码量
使用装饰器
使用function定义一个回调函数对象,然后在派生类中赋值bind后的函数,就能够直接使用基类的方法调用到派生类的函数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include<functional> #include<iostream>
class Base{ public: void interface(){ callback(); }; protected: std::function<void()> callback; };
class DerivedB:public Base{ public: DerivedB(){ callback=std::bind(&DerivedB::func1,this); } private: void func1(){ std::cout<<"func1"<<"\n"; } };
int main(){ DerivedB db; db.interface(); return 0; }
|
wrapper的意义
场景
假设一个网络服务器应用,在服务响应架构设计时,我们分为两个模块: 回调实现模块 和 回调函数调用模块.
使用函数指针定义回调函数时,不同响应行为,回调函数的参数或者返回类型不一样,此时无法实现一个通用的函数完成该模块.
只能定义一个接口,在每个响应行为拍摄类中重载这个接口,完成回调功能.
但是使用function定义回调函数对象,将该对象定义为一个无参的函数,具体的封装由派生类中实现.
我们在基类中就能完成回调函数调用模块.
在派生类中只需要把对应行为的回调函数和参数封装,传递给基类指针即可.
优点
- 能更简单的实现多态性
- 能提升应用的模块化程度