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;//包装普通全局变量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;
}

相关推荐

  1. C++调用对象包装

    2024-04-28 12:18:02       11 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-28 12:18:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-28 12:18:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-28 12:18:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-28 12:18:02       18 阅读

热门阅读

  1. 探索Kotlin:最佳学习实践和资源指南

    2024-04-28 12:18:02       10 阅读
  2. XSS攻击

    XSS攻击

    2024-04-28 12:18:02      10 阅读
  3. 墨子时事周报

    2024-04-28 12:18:02       11 阅读
  4. C# 字符串左不足位数时补充0

    2024-04-28 12:18:02       12 阅读
  5. transformers - 预测中间词

    2024-04-28 12:18:02       11 阅读
  6. opencv动态识别人脸

    2024-04-28 12:18:02       10 阅读
  7. L2-052 吉利矩阵

    2024-04-28 12:18:02       11 阅读
  8. Centos编译安装python3.9

    2024-04-28 12:18:02       13 阅读
  9. 生成对抗网络(GAN)

    2024-04-28 12:18:02       11 阅读
  10. 《AI音频类工具之二—— 讯飞智作》

    2024-04-28 12:18:02       11 阅读