0%

C++11 wrapper装饰器 bind+function

原文链接: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部分

按绑定函数类型分:

  1. 普通函数
  2. 普通成员函数
  3. 静态成员函数

按参数类型分:

  1. 普通参数
  2. 引用参数
  3. 占位符

特性

  1. const类型参数在bind里面基本上没啥意义,普通参数bind之后,对应参数的值也不会变化
  2. 占位符必须从_1开始递增使用
  3. 占位符不能替代引用参数, 引用参数能在函数内被修改,占位符不能实现这个特性

示例

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;

//1.普通函数
auto func1=std::bind(func,a,std::ref(b));
b=2;
std::cout<<"1 普通函数 sum(a,ref(b))="<<func1()<<"\n";


//2. 普通成员函数
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";

//3. 静态成员函数
auto func3=std::bind(&C::scall,a,std::ref(b));
b=4;
std::cout<<"3 静态成员函数 C.sum(a,ref(b))="<<func3()<<"\n";

//4.使用占位符
auto func4=std::bind(func,_1,std::ref(b)); //_1到_20,占位符必须对应参数位置
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) 调用

//与bind搭配使用
std::function<int()> fobj_bind=std::bind(func,a,std::ref(b));
使用fobj() 无参调用,并且参数b是引用传值

//函数替换 定义的函数可以通过bind再替换为其他函数
fobj_bind=std::bind(func,a,-a);

特性

function对象相比函数指针,主要的优势

  1. 简单直观 function定义更符合对象定义,而指针类型定义复杂
  2. 灵活 函数对象存放的函数可以替换
  3. 优化 函数对象编译时会自动内联调用 函数指针无法实现
  4. 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;
}
//output:
// tmp(1,2)=3
// sum(1,2)=3

但是类的成员函数有所不同,例如类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定义回调函数对象,将该对象定义为一个无参的函数,具体的封装由派生类中实现.
我们在基类中就能完成回调函数调用模块.

在派生类中只需要把对应行为的回调函数和参数封装,传递给基类指针即可.

优点

  1. 能更简单的实现多态性
  2. 能提升应用的模块化程度