C++可调用对象的绑定器和包装器

包装器和绑定器

  • 乃神器也
  • 可调用对象、包装器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;//包装普通全局变量showfunction<Fun> fn11 = show;//包装普通全局变量函数show,用别名了fn1(1, "我是一只傻傻鸟");//用function对象调用普通全局函数showfn11(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);//绑定普通全局变量showfn1(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);//绑定普通全局变量showfn1(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;//缓冲队列,底层容器用dequefunction<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);//创建消费者线程t1thread t2(&AA::outcache, &aa);//创建消费者线程t2thread t3(&AA::outcache, &aa);//创建消费者线程t3this_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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/4115.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uniapp微信小程序开发踩坑日记:Vue3 + uniapp项目引入Echarts图表库

一、下载插件包 下载地址如下&#xff1a; lime-echart: 百度图表 echarts&#xff0c;uniapp、taro 使用 echarts 图表&#xff0c;全面兼容各平台小程序、H5、APP、Nvue 将以下两个文件夹放到项目的components里 同样地&#xff0c;将静态资源文件夹下内容放到自己项目的s…

什么是端口

啊&#xff0c;端口&#xff01;这可是计算机网络中一个非常重要的概念呢。 简单来说&#xff0c;端口就好比是网络通信中的门&#xff0c;用来区分不同的应用程序或服务。我们知道&#xff0c;计算机在进行网络通信时需要通过网络传输数据&#xff0c;而端口就是帮助计算机在…

vue echarts 饼图(环形图)

vue echarts 饼图(环形图) &#xff0c;echarts版本为5.3.3 可以自定义颜色 <template><div><div id"pieChart1" ref"pieChartRef1" style"width: 100%; height: 250px"></div></div></template><scri…

ASP.NET教务平台—学籍管理模块开发与设计

摘 要 教务平台之学籍管理模块是一个典型的教务信息管理系统(MIS)&#xff0c;其开发主要包括后台数据库的建立和前端应用程序的开发两个方面。对于后台数据库要求实现数据的完整性、一致性和安全性&#xff1b;对于前台应用程序开发则要求模块功能完备、界面友好、易使用等特…

java中http调用组件深入详解

目录 一、前言 二、http调用概述 2.1 什么是http调用 2.1.1 http调用步骤 2.2 HTTP调用特点 2.3 HTTP调用应用场景 三、微服务场景下http调用概述 3.1 微服务开发中http调用场景 3.2 微服务组件中http的应用 四、常用的http调用组件 4.1 java中常用的http组件介绍 4…

C++11新特性:lambda表达式

目录 1.lambda表达式 1.1 C98中的一个例子 1.2 lambda表达式 1.3 lamzbda表达式语法 1. lambda表达式各部分说明 2. 捕获列表说明 1.4 函数对象与lambda表达式 1.lambda表达式 1.1 C98中的一个例子 在C98中&#xff0c;如果想要对一个数据集合中的元素进行排序&#xff0…

Kafka 3.x.x 入门到精通(04)——对标尚硅谷Kafka教程

Kafka 3.x.x 入门到精通&#xff08;04&#xff09;——对标尚硅谷Kafka教程 2. Kafka基础2.1 集群部署2.2 集群启动2.3 创建主题2.4 生产消息2.5 存储消息2.5.1 存储组件2.5.2 数据存储2.5.2.1 ACKS校验2.5.2.2 内部主题校验2.5.2.3 ACKS应答及副本数量关系校验2.5.2.4 日志文…

BEC写作和其他英语写作有什么区别?成人学英语去哪里柯桥有专业培训吗?

BEC中级考试的写作与其他英语类考试略有不同。除考查考生的整体写作水平之外&#xff0c;它也考查考生处理日常商务活动及解决商务运作中出现问题的能力。测试题材与体裁均与商务信函有关&#xff0c;往往涉及以下内容&#xff1a; 商务信函&#xff1a;这里所涉及的信函往往是…

CNAS软件测评报告收费标准

随着信息技术的快速发展&#xff0c;软件测评在保障软件质量、提升用户体验等方面扮演着越来越重要的角色。CNAS&#xff08;中国合格评定国家认可委员会&#xff09;作为国内权威的认可机构&#xff0c;其软件测评报告收费标准受到了广泛关注。本文旨在解析CNAS软件测评报告的…

(学习日记)2024.05.06:UCOSIII第六十节:User文件夹函数概览(uCOS-III->Source文件夹)第六部分

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

CentOS安装CRI--containerd

前言 CRI&#xff0c;Container Runtimes&#xff0c;通常直译成容器进行时因为kubernetes&#xff0c;从1.24开始&#xff0c;移除了Dockershim&#xff0c;需要额外安装CRI&#xff0c;保障Pod能顺利运行。网上有很多容器进行时的工具&#xff0c;本文采用containerd工具。 …

解决双击PDF文件出现打印的问题【Adobe DC】

问题描述 电脑安装Adobe Acrobat DC之后&#xff0c;双击PDF文件就会出现打印&#xff0c;而无法直接打开。 右键PDF文件就会发现&#xff0c;第一栏出现的不是用Adobe打开&#xff0c;而是打印。 重装软件多次仍然无法解决。 原因 右键菜单被改写了。双击其实是执行右键菜…

AIGC的发展历程

AI生成内容&#xff08;AIGC&#xff09;的发展历程可以追溯到20世纪50年代&#xff0c;当时人工智能&#xff08;AI&#xff09;的概念还处于起步阶段。然而&#xff0c;AIGC技术的快速发展主要集中在21世纪初&#xff0c;特别是随着深度学习、自然语言处理和其他相关领域的突…

异次元店铺商品系统自带支付源码

异次元店铺系统是荔枝店铺系统3.0的完全重构版本&#xff0c;从零开始编写&#xff0c;采用原生php开发。数据库底层使用Eloquent ORM&#xff0c;模板渲染使用Smarty3.1以及PHP原生渲染&#xff0c;会话保持全程使用session。以下是一些主要功能的简要介绍&#xff1a; 下 载…

每天学习一个Linux命令之sort

每天学习一个Linux命令之sort 引言 在Linux系统中&#xff0c;sort命令被广泛用于对文本文件进行排序操作。它可以按照指定的字段、行、列或者数字进行排序。本篇博客将介绍sort命令的使用方法和可用选项&#xff0c;并详细说明每个选项的用法。 sort命令简介 sort命令用于…

MySQL常见问题与解决方案详述

MySQL&#xff1a;常见问题与解决方案详述 作为一款广泛使用的开源关系型数据库管理系统&#xff0c;MySQL对于初学者来说既充满吸引力又充满挑战。本文将列举初学者在使用MySQL过程中可能遇到的一些典型问题&#xff0c;并提供详细的解决方案&#xff0c;配以图片辅助说明&am…

【漏洞复现】艺创科技智能营销路由器后台命令执行漏洞

漏洞描述&#xff1a; 成都艺创科技有限公司是一家专注于新型网络设备研发、生产、销售和服务的企业&#xff0c;在大数据和云时代&#xff0c;致力于为企业提供能够提升业绩的新型网络设备。 智能营销路由器存在后台命令执行漏洞&#xff0c;攻击者可利用漏洞获取路由器控制…

Three CSS2D 渲染器 月球绕地球旋转

CSS2DRenderer(CSS 2D渲染器) CSS2DRenderer&#xff08;CSS 2D渲染器&#xff09;可以把HTML元素作为标签标注到三维场景中&#xff0c;CSS2DRenderer是CSS3DRenderer&#xff08;CSS 3D渲染器&#xff09;的简化版本&#xff0c;它唯一支持的变换是位移。通过CSS2DRenderer我…

vue身份证检验方法

1.定义一个input输入框 <inputtype"text"v-model"personnelInformationForm.idNo"class"qrcode-main-form-li-input"maxlength"18" /><button class"qrcode-form-submit" click"submitForm">提 交&l…

AI预测福彩3D第9套算法实战化测试第5弹2024年4月27日第5次测试

今天继续进行新算法的测试&#xff0c;今天是第5次测试。好了&#xff0c;废话不多说了&#xff0c;直接上图上结果。 2024年4月27日福彩3D预测结果 6码定位方案如下&#xff1a; 百位&#xff1a;3、5、6、2、7、1 十位&#xff1a;8、4、9、3、1、0 个位&#xff1a;3、5、2、…