包装器和绑定器
- 乃神器也
- 可调用对象、包装器std:function、绑定器std:bind
- 应用场景:可变函数和参数、回调函数、取代虚函数
可调用对象
在C++中,可以像函数一样调用的有:普通函数、类的静态成员函数、仿函数、lambda函数、类
的成员函数、可被转换为函数的类的对象,统称可调用对象或函数对象。
可调用对象有类型,可以用指针存储它们的地址,可以被引用(类的成员函数除外)
普通函数
普通函数类型可以声明函数、定义函数指针和函数引用,但是,不能定义函数的实体。
#include<algorithm>
#include<iostream>
using namespace std;
using Fun = void(int, const string&);//普通函数类型别名
Fun show;//声明普通函数
//void show(int, const string&);//声明普通函数
int main() {
show(1, "我是一只小小鸟");//直接调用普通函数
void(*fp1)(int, const string&) = show;//声明函数指针,指向普通函数
void(&fr1)(int, const string&) = show;//声明函数指针,引用普通函数
fp1(2, "我是一只傻傻鸟");//用函数指针调用普通函数
fr1(3, "我是一只傻傻鸟");//用函数引用调用普通函数
//下面是C++写法
Fun* fp2 = show;//声明函数指针,指向普通函数
Fun& fr2 = show;//声明函数引用,指向普通函数
fp2(4, "我是一只傻傻鸟");//用函数指针调用普通函数
fr2(5, "我是一只傻傻鸟");//用函数引用调用普通函数
}
//定义普通函数
void show(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
//以下代码是错误的,不能用函数类型定义函数的实体
//Func show1{
// cout << "亲爱的" << bh << "," << message << endl;
//}
类的静态成员函数
类的静态成员函数和普通函数本质上是一样的,把普通函数放在类中而已。
#include<algorithm>
#include<iostream>
using namespace std;
using Fun = void(int, const string&);//普通函数类型别名
Fun show;//声明普通函数
//void show(int, const string&);//声明普通函数
struct AA {
static void show(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
};
int main() {
AA::show(1, "我是一只傻傻鸟。");// 直接调用静态成员函数。
void(*fp1)(int, const string&) = AA::show; //用函数指针指向静态成员函数。
void(&fr1)(int, const string&) = AA::show;//引用静态成员函数。
fp1(2, "我是一只傻傻鸟。");//用函数指针调用静态成员函数。 -
fr1(3, "我是一只傻傻鸟。");//用函数引用调用静态成员函数。
Fun * fp2 = AA::show;//用函数指针指向静态成员函数。
Fun& fr2 = AA::show;//引用静态成员函数。
fp2(4, "我是一只傻傻鸟。");//用函数指针调用静态成员函数。
fr2(5, "我是一只傻傻鸟。");//用函数引用调用静态成员函数。
}
仿函数
仿函数的本质是类,调用的代码像函数。
仿函数的类型就是类的类型。
#include<algorithm>
#include<iostream>
using namespace std;
struct BB {//仿函数
void operator()(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
};
int main() {
BB bb;
bb(11, "我是一只傻傻鸟");//用对象调用仿函数
BB()(12, "我是一只傻傻鸟");//用匿名对象调用仿函数。
BB& br = bb;//引用函数
br(13, "我是一只傻傻鸟");//用对象引用调用仿函数
}
lambda函数
lambda函数的本质是仿函数,仿函数的本质是类。
#include<algorithm>
#include<iostream>
using namespace std;
int main() {
//创建lambda对象
auto lb = [](int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
};
auto& lr = lb;
lb(1, "我是一只傻傻鸟");
lr(2, "我是一只傻傻鸟");
}
类的非静态成员函数
类的非静态成员函数只有指针类型,没有引用类型,不能引用。
类的非静态成员函数有地址,但是,只能通过类的对象才能调用它,所以,C++对它做了特别处理。
类的非静态成员函数只有指针类型,没有引用类型,不能引用。
#include<algorithm>
#include<iostream>
using namespace std;
struct CC {
void show(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
};
int main() {
CC cc;
cc.show(14, "我是一只傻傻鸟。");
//void (*fp11)(int, const string&);//这是普通函数指针,多了CC::
void (CC:: * fp11)(int, const string&) = &CC::show;//定义类的成员函数的指针
(cc.*fp11)(15, "我是一只傻傻鸟。");///用类的成员函数的指针调用成员函数。
using pFun = void (CC::*)(int, const string&);//类成员函数的指针类型。
pFun fp12 = &CC::show;// 让类成员函数的指针指向类的成员函数的地址
(cc.*fp12)(16, "我是一只傻傻鸟。");//用类成员函数的指针调用类的成员函数。
}
可被转换为函数指针的类对象
类可以重载类型转换运算符operator数据类型()
,如果数据类型是函数指针或函数引用类型,那么
该类实例也将成为可调用对象。
它的本质是类,调用的代码像函数。
在实际开发中,意义不大。
包装器function
std:function模板类是一个通用的可调用对象的包装器,用简单的、统一的方式处理可调用对象。
template<class _Fty>
class function...
_Fty
是可调用对象的类型,格式:返回类型(参数列表) 。
包含头文件:#include <functional>
注意:
-
重载了bool运算符,用于判断是否包装了可调用对象。
-
如果std::function对象未包装可调用对象,使用std:function对象将抛出std:bad_function_call异常。
这里要注意这个类的非静态成员函数,我们包装的时候多一个参数://类的非静态成员函数
CC cc;
void (CC:: * fp11)(int, const string&) = &CC::show;//定义类的成员函数指针
(cc.*fp11)(5, "我是一只傻傻鸟"); //用类的成员函数指针调用类的成员函数
function<void(CC&,int, const string&)> fn12 = &CC::show;//包装类的成员,多了一个参数
fn12(cc,5, "我是一只傻傻鸟");//用function对象调用类的成员,这个比较特殊,需要加上实例化的成员才能调用
这样显得很不通用,包装器可以解决这个问题
#include<algorithm>
#include<iostream>
#include<functional>
using namespace std;
//普通函数
void show(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
//using Fun=void(int,const string&);//函数类型的别名
struct AA {//类中有静态成员函数
static void show(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
};
struct BB {//仿函数
void operator()(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
};
struct CC {//仿函数
void show(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
};
struct DD {//可以转换为普通函数指针的类
using Fun = void (*)(int, const string&);//函数指针别名
operator Fun() {
return show;//返回普通函数show的地址
}
};
int main() {
using Fun = void(int, const string&);//函数类型的别名
//普通函数
void(*fp1)(int, const string&) = show;//声明函数指针,指向函数对象
fp1(1, "我是一只傻傻鸟"); //用函数指针调用普通函数
//function<返回类型(参数列表)>;//包装普通函数
function<void(int, const string&)> fn1=show;//包装普通全局变量show
function<Fun> fn11 = show;//包装普通全局变量函数show,用别名了
fn1(1, "我是一只傻傻鸟");//用function对象调用普通全局函数show
fn11(1, "我是一只傻傻鸟");
//类的静态成员函数
void(*fp3)(int, const string&) = AA::show;//用函数指针指向类的静态成员
fp3(2, "我是一只傻傻鸟");//用函数指针调用类的静态成员
function<void(int, const string&)> fn3 = AA::show;//包装类的静态成员函数
fn3(2, "我是一只傻傻鸟");//用function对象调用类的静态成员函数
仿函数
BB bb;
bb(3, "我是一只傻傻鸟");//用仿函数对象调用仿函数
function<void(int, const string&)> fn4 = BB();//包装仿函数
fn4(3, "我是一只傻傻鸟");//用function对象调用仿函数
//创建lambda对象
auto lb = [](int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
};
lb(4, "我是一只傻傻鸟");
function<void(int, const string&)> fn5 = lb;//包装lambda对象
fn5(4, "我是一只傻傻鸟");//用function对象调用lambda对象
//类的非静态成员函数
CC cc;
void (CC:: * fp11)(int, const string&) = &CC::show;//定义类的成员函数指针
(cc.*fp11)(5, "我是一只傻傻鸟"); //用类的成员函数指针调用类的成员函数
function<void(CC&,int, const string&)> fn12 = &CC::show;//包装类的成员,多了一个参数
fn12(cc,5, "我是一只傻傻鸟");//用function对象调用类的成员,这个比较特殊,需要加上实例化的成员才能调用
//可以被转化为函数指针的类对象
DD dd;
dd(6, "我是一只傻傻鸟");//用可以被转化为函数指针的类对象调用普通函数
function<void(int, const string&)> fn6 = dd;//包装类的成员
fn6(5, "我是一只傻傻鸟");//用function对象调用类的成员,这个比较特殊,需要加上实例化的成员才能调用
}
绑定器bind
std:.bind()
模板函数是一个通用的函数适配器(绑定器),它用一个可调用对象及其参数,生成一
个新的可调用对象,以适应模板。
包含头文件#include<functional>
函数原型:
template<class Fx, class... Args >
function<> bind (Fx&& fx,Args&...args);
Fx
:需要绑定的可调用对象(可以是前两节课介绍的那六种,也可以是function对象)。
args
:绑定参数列表,可以是左值、右值和参数占位符std::placeholders::_n
,如果参数不是占位符,缺省为值传递,std:: ref
(参数)则为引用传递。
std::bind()
返回std:function
的对象。
例如:普通函数绑定:
普通函数:
//普通函数
void show(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
这个show函数需要两个参数,所以绑定器后面用两个占位符,placeholders::_1表示function对象第一个参数的绑定,placeholders::_2表示function对象第二个参数的绑定
function<void(int, const string&)>fn1 = show;
function<void(int, const string&)>fn2 = bind(show,placeholders::_1,placeholders::_2);
//这个show需要两个参数,所以我们后面用两个占位符,placeholders::_1表示function对象第一个参数放的位置,placeholders::_2表示function对象第二个参数放的位置
fn1(1, "我是一只小小鸟");
fn2(2, "我是一只小小鸟");
绑定器如果调换参数位置也是可以的,如果现有的函数类型与要求的函数类型不同,那么可以用它对现有的函数对象进行转换,生成新的函数对象,与要求的函数类型匹配上.
//我们调换一下也可以适配,就是如果现有的函数类型与要求的函数类型不同,那么可以用它对现有的函数对象进行转换,生成新的函数对象,与要求的函数类型匹配上
function<void(const string&,int)>fn3 = bind(show, placeholders::_2, placeholders::_1);
fn3( "我是一只小小鸟",3);
绑定器缺少一个参数,我们可以提前绑定。
function<void(const string&)>fn4 = bind(show, 3, placeholders::_1);
fn4("我是一只小小鸟");
绑定器多一个参数,多的那个可以随便写
function<void(int,const string&,int)>fn5 = bind(show, placeholders::_1, placeholders::_2);
fn5(1,"我是一只小小鸟",99)
总代码:
#include<algorithm>
#include<iostream>
#include<functional>
using namespace std;
//普通函数
void show(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
int main() {
function<void(int, const string&)>fn1 = show;
function<void(int, const string&)>fn2 = bind(show,placeholders::_1,placeholders::_2);
//这个show需要两个参数,所以我们后面用两个占位符,placeholders::_1表示function对象第一个参数放的位置,placeholders::_2表示function对象第二个参数放的位置
fn1(1, "我是一只小小鸟");
fn2(2, "我是一只小小鸟");
//我们调换一下也可以适配,就是如果现有的函数类型与要求的函数类型不同,那么可以用它对现有的函数对象进行转换,生成新的函数对象,与要求的函数类型匹配上
function<void(const string&,int)>fn3 = bind(show, placeholders::_2, placeholders::_1);
fn3( "我是一只小小鸟",3);
//缺少一个参数,我们可以提前绑定。
function<void(const string&)>fn4 = bind(show, 3, placeholders::_1);
fn4("我是一只小小鸟");
//多一个参数,多的那个可以随便写
function<void(int,const string&,int)>fn5 = bind(show, placeholders::_1, placeholders::_2);
fn5(1,"我是一只小小鸟",99);
}
我们绑定上面六种函数
注意对于类的非静态函数,我们绑定的时候可以先把类名的那个参数写上,这样参数就和普通函数一样了,可以用于模板,也就是说通过bind适配器将六种对象统一了
还有一点写类的非静态函数的时候,第一个参数填类成员函数的地址,第二个参数填对象的地址,然后是可变参数。像这个一样bind(&CC::show, &cc, placeholders::_1, placeholders::_2);
//这里我们第一个参数填类成员函数的地址,第二个参数填对象的地址
function<void(int, const string&)> fn13 = bind(&CC::show, &cc, placeholders::_1, placeholders::_2);//绑定类的成员,也要绑定三个
fn13(5, "我是一只傻傻鸟");//用function对象调用类的成员,这个比较特殊,需要加上实例化的成员才能调用
代码:
#include<algorithm>
#include<iostream>
#include<functional>
using namespace std;
//普通函数
void show(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
//using Fun=void(int,const string&);//函数类型的别名
struct AA {//类中有静态成员函数
static void show(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
};
struct BB {//仿函数
void operator()(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
};
struct CC {//仿函数
void show(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
};
struct DD {//可以转换为普通函数指针的类
using Fun = void (*)(int, const string&);//函数指针别名
operator Fun() {
return show;//返回普通函数show的地址
}
};
int main() {
//function<返回类型(参数列表)>;//包装普通函数
function<void(int, const string&)> fn1=bind(show,placeholders::_1,placeholders::_2);//绑定普通全局变量show
fn1(1, "我是一只傻傻鸟");//用function对象调用普通全局函数show
//类的静态成员函数
function<void(int, const string&)> fn3 = bind(AA::show, placeholders::_1, placeholders::_2);//绑定类的静态成员函数
fn3(2, "我是一只傻傻鸟");//用function对象调用类的静态成员函数
仿函数
BB bb;
function<void(int, const string&)> fn4 = bind(BB(), placeholders::_1, placeholders::_2);//绑定仿函数
fn4(3, "我是一只傻傻鸟");//用function对象调用仿函数
//创建lambda对象
auto lb = [](int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
};
lb(4, "我是一只傻傻鸟");
function<void(int, const string&)> fn5 = bind(lb, placeholders::_1, placeholders::_2);//绑定lambda对象
fn5(4, "我是一只傻傻鸟");//用function对象调用lambda对象
//类的非静态成员函数
CC cc;
//function<void(CC&,int, const string&)> fn12 = bind(&CC::show, placeholders::_1, placeholders::_2,placeholders::_3);//绑定类的成员,也要绑定三个
//fn12(cc,5, "我是一只傻傻鸟");//用function对象调用类的成员,这个比较特殊,需要加上实例化的成员才能调用
//为了和其他的一样,我们这里可以把对象名提前绑定,然后前面就和其他一样了
function<void(int, const string&)> fn13 = bind(&CC::show, &cc, placeholders::_1, placeholders::_2);//绑定类的成员,也要绑定三个
fn13(5, "我是一只傻傻鸟");//用function对象调用类的成员,这个比较特殊,需要加上实例化的成员才能调用
//可以被转化为函数指针的类对象
DD dd;
function<void(int, const string&)> fn6 = bind(dd, placeholders::_1, placeholders::_2);;//绑定类的成员
fn6(5, "我是一只傻傻鸟");//用function对象调用类的成员,这个比较特殊,需要加上实例化的成员才能调用
}
或者说我们也可以用auto
代替前面写的那些
#include<algorithm>
#include<iostream>
#include<functional>
using namespace std;
//普通函数
void show(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
//using Fun=void(int,const string&);//函数类型的别名
struct AA {//类中有静态成员函数
static void show(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
};
struct BB {//仿函数
void operator()(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
};
struct CC {//仿函数
void show(int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
}
};
struct DD {//可以转换为普通函数指针的类
using Fun = void (*)(int, const string&);//函数指针别名
operator Fun() {
return show;//返回普通函数show的地址
}
};
int main() {
//function<返回类型(参数列表)>;//包装普通函数
auto fn1=bind(show,placeholders::_1,placeholders::_2);//绑定普通全局变量show
fn1(1, "我是一只傻傻鸟");//用function对象调用普通全局函数show
//类的静态成员函数
auto fn3 = bind(AA::show, placeholders::_1, placeholders::_2);//绑定类的静态成员函数
fn3(2, "我是一只傻傻鸟");//用function对象调用类的静态成员函数
仿函数
BB bb;
auto fn4 = bind(BB(), placeholders::_1, placeholders::_2);//绑定仿函数
fn4(3, "我是一只傻傻鸟");//用function对象调用仿函数
//创建lambda对象
auto lb = [](int bh, const string& message) {
cout << "亲爱的" << bh << "," << message << endl;
};
lb(4, "我是一只傻傻鸟");
auto fn5 = bind(lb, placeholders::_1, placeholders::_2);//绑定lambda对象
fn5(4, "我是一只傻傻鸟");//用function对象调用lambda对象
//类的非静态成员函数
CC cc;
//function<void(CC&,int, const string&)> fn12 = bind(&CC::show, placeholders::_1, placeholders::_2,placeholders::_3);//绑定类的成员,也要绑定三个
//fn12(cc,5, "我是一只傻傻鸟");//用function对象调用类的成员,这个比较特殊,需要加上实例化的成员才能调用
//为了和其他的一样,我们这里可以把对象名提前绑定,然后前面就和其他一样了
auto fn13 = bind(&CC::show, &cc, placeholders::_1, placeholders::_2);//绑定类的成员,也要绑定三个
fn13(5, "我是一只傻傻鸟");//用function对象调用类的成员,这个比较特殊,需要加上实例化的成员才能调用
//可以被转化为函数指针的类对象
DD dd;
auto fn6 = bind(dd, placeholders::_1, placeholders::_2);;//绑定类的成员
fn6(5, "我是一只傻傻鸟");//用function对象调用类的成员,这个比较特殊,需要加上实例化的成员才能调用
}
包装器和绑定器的三种应用场景
可变函数和参数
写一个函数,函数的参数是函数对象及参数,功能和thread类的构造函数相同。
#include<algorithm>
#include<iostream>
#include<functional>
#include<thread>
using namespace std;
void show0() {
cout << "亲爱的,我是一只傻傻鸟\n";
}
void show1(const string& message) {
cout << "亲爱的," << message << endl;
}
struct CC {//类中有普通成员函数
void show2(int bh, const string& message) {
cout << "亲爱的," <<bh<<",号" << message << endl;
}
};
template<typename Fn,typename...Args>
void show(Fn fn, Args...args) {
cout << "表白前的准备工作...\n";
auto f = bind(fn, args...);
f();
cout << "表白完成\n";
}
int main() {
show(show0);
show(show1, "我是一只傻傻鸟");
CC cc;
show(&CC::show2, &cc, 3, "我是一只傻傻鸟");
/*thread t1(show0);
thread t2(show1, "我是一只傻傻鸟");
CC cc;
thread t3(&CC::show2, &cc, 3, "我是一只傻傻鸟");
t1.join();
t2.join();
t3.join();*/
}
回调函数的实现
在消息队列和网络库的框架中,当接收到消息(报文)时,回调用户自定义的函数对象,把消息(报文)参数传给它,由它决定如何处理。
这里我们用多线程中生产者消费者模型演示,在消费者线程的任务函数outcache(),我们之前在出队后,休眠一毫秒,假装处理数据。现在,我们要增加处理数据的功能。我们不可能直接把处理数据的代码写在哪里,太多太难看了。我们定义一个成员函数,代码好看一点,逻辑也更加清晰。但是,如果用成员函数,就会修改这个类(我们将生产者消费者封装成了一个类)。假设这个类是一个开发框架,框架本身的代码是不能随便修改的。在实际开发中,框架归框架,业务归业务。不可能处理每种业务都要修改框架。所以最好的方法就是用回调函数。
#include<iostream>
#include<algorithm>
#include<string>
#include<mutex>
#include<deque>
#include<queue>
#include<condition_variable>
#include<functional>
using namespace std;
void show(const string& message) {//处理业务的普通函数
cout << "处理数据:" << message << endl;
}
struct BB {//处理业务的类
void show(const string& message) {
cout << "处理表白数据:" << message << endl;
}
};
class AA {
mutex m_mutex;//互斥锁
condition_variable m_cond;//条件变量
queue<string, deque<string> > m_q;//缓冲队列,底层容器用deque
function<void(const string&)> m_callback;//回调函数对象
public:
//注册回调函数,回调函数只有一个参数(消费者收到的数据)
template<typename Fn,typename ...Args>
void callback(Fn&& fn, Args&&...args) {
m_callback = bind(forward<Fn>(fn), forward<Args>(args)..., std::placeholders::_1);//绑定回调
}
//第二个参数是可变参数包,如果传进来的可调用对象是类的成员函数,那么,需要把对象的this指针传进来,可变参数包中将
//有一个参数,如果传进来的是可调用对象不是类的成员函数。可变参数包中就没有参数了。第三个参数是占位符.因为框架调用回调函数的时候,会把数据传进来
void incache(int num)//生产数据,num指定数据的个数
{
lock_guard<mutex> lock(m_mutex);//申请加锁
for (int i = 0;i < num;i++) {
static int bh = 1;//超女编号
string message = to_string(bh++) + "号超女";//拼接出一个数据
m_q.push(message);//把生产出来的数据入队
}
m_cond.notify_one();//唤醒一个当前条件变量堵塞的线程
//m_cond.notify_all();//唤醒全部当前条件变量堵塞的线程
}
void outcache() {//消费者线程任务函数
while (true) {
string message;//存放出队的数据
//这个作用域的作用是让他立刻释放锁,数据处理完出队之后立刻释放锁
//把互斥锁转化成unique_lock<mutex>,并申请加锁
unique_lock<mutex> lock(m_mutex);
//条件变量虚假唤醒:消费者线程被唤醒后,缓存队列中没有数据
while (m_q.empty())//如果队列空,进入循环,负责直接处理数据,必须用循环,不能用if
m_cond.wait(lock);//等待生产者的唤醒信号
//m_cond.wait(lock, [this] {return !m_q.empty();});
//上面和while函数一样,也有一个while
//数据出队
message = m_q.front();
m_q.pop();cout << "线程:" << this_thread::get_id() << "," << message << endl;
lock.unlock();//手工解锁,这样就不用作用域了
//处理出队的数据(把数据消费掉)
this_thread::sleep_for(chrono::milliseconds(1));//假设处理数据需要1毫秒
if (m_callback) m_callback(message);//回调函数,把收到的数据传给他。
}
}
};
int main() {
AA aa;
//先注册回调函数
//aa.callback(show);
BB bb;
aa.callback(&BB::show,&bb);//类的非成员函数,第一个参数填类成员函数的地址,第二个参数填对象的地址
thread t1(&AA::outcache, &aa);//创建消费者线程t1
thread t2(&AA::outcache, &aa);//创建消费者线程t2
thread t3(&AA::outcache, &aa);//创建消费者线程t3
this_thread::sleep_for(chrono::seconds(2));//休眠2秒
aa.incache(3);//生产三个数据
this_thread::sleep_for(chrono::seconds(2));//休眠3秒
aa.incache(5);//生产三个数据
t1.join();
t2.join();
t3.join();
}
如何取代虚函数
C++虚函数在执行过程中会跳转两次(先查找对象的函数表,再次通过该函数表中的地址找到真正的执行地址),这样的话,CPU会跳转两次,而普通函数只跳转一次。
CPU每跳转一次,预取指令要作废很多,所以效率会很低。(百度)
为了管理的方便(基类指针可指向派生类对象和自动析构派生类),保留类之间的继承关系。
#include<iostream>
#include<algorithm>
#include<string>
#include<mutex>
#include<deque>
#include<queue>
#include<condition_variable>
#include<functional>
using namespace std;
struct Hero {//英雄基类
virtual void show() { cout << "英雄释放了技能\n"; }
};
struct XS :public Hero {//西施派生类
void show() { cout << "西施释放了技能\n"; }
};
struct HX :public Hero {//韩信派生类
void show() { cout << "韩信释放了技能\n"; }
};
int main() {
//根据用户选择的英雄,释放一技能,二技能和大招
int id = 0;
cout << "请输入英雄(1-西施;2-韩信):";
cin >> id;
//创建基类指针,让他指向派生类对象,用基类指针调用派生类的成员函数
Hero* ptr = nullptr;
if (id == 1) {//1-西施
ptr = new XS;
}
else if (id == 2) {//2-韩信
ptr = new HX;
}
if (ptr != nullptr) {
ptr->show();//用基类指针调用派生类的成员函数
delete ptr;//释放派生类对象
}
return 0;
}
对于上面这个,我们用包装器和绑定器实现与虚函数相同的功能。注意绑定器和包装器,不要求两个类之间是否有继承关系。
#include<iostream>
#include<algorithm>
#include<string>
#include<mutex>
#include<deque>
#include<queue>
#include<condition_variable>
#include<functional>
using namespace std;
struct Hero {//英雄基类
//virtual void show() { cout << "英雄释放了技能\n"; }
function<void()>m_callback;//用于绑定子类的成员函数
//注册子类成员函数的模板函数
template<typename Fn,typename ...Args>
void callback(Fn&& fn, Args&&...args) {
m_callback = bind(forward<Fn>(fn), forward<Args>(args)...);
}
void show() { m_callback(); }//调用子类的成员函数
};
struct XS :public Hero {//西施派生类
void show() { cout << "西施释放了技能\n"; }
};
struct HX :public Hero {//韩信派生类
void show() { cout << "韩信释放了技能\n"; }
};
int main() {
//根据用户选择的英雄,释放一技能,二技能和大招
int id = 0;
cout << "请输入英雄(1-西施;2-韩信):";
cin >> id;
//创建基类指针,让他指向派生类对象,用基类指针调用派生类的成员函数
Hero* ptr = nullptr;
if (id == 1) {//1-西施
ptr = new XS;
ptr->callback(&XS::show, static_cast<XS*>(ptr));//注册回调函数
}
else if (id == 2) {//2-韩信
ptr = new HX;
ptr->callback(&HX::show, static_cast<HX*>(ptr));//注册回调函数
}
if (ptr != nullptr) {
ptr->show();//用基类指针调用派生类的成员函数
delete ptr;//释放派生类对象
}
return 0;
}