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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Flutter】GetX

前言 状态管理 / 路由管理 / 依赖管理 这三部分之间存在联系 参考文章 建议看官网文章&#xff0c;很详细 &#xff0c;pub.dev搜索get pub.dev的文档 状态管理文章相关链接 状态管理 案例 实现一个计算器&#xff0c;运用GetX去管理它 构建界面 构建一个计算器界面 …

基于SpringBoot的“房产销售平台”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“房产销售平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统整体模块图 登录窗口界面 房源信息管理窗口界…

解决HttpServletRequest中的InputStream/getReader只能被读取一次的问题

一、事由 由于我们业务接口需要做签名校验&#xff0c;但因为是老系统了签名规则被放在了Body里而不是Header里面&#xff0c;但是我们不能在每个Controller层都手动去做签名校验&#xff0c;这样不是优雅的做法&#xff0c;然后我就写了一个AOP&#xff0c;在AOP中实现签名校…

Linux--进程控制(2)--进程的程序替换(夺舍)

目录 进程的程序替换 0.相关函数 1.先看现象 2.解释原理 3.将代码改成多进程版 4.使用其它的替换函数&#xff0c;并且认识函数参数的含义 5.其它 进程的程序替换 0.相关函数 关于进程替换我们需要了解的6个函数&#xff1a; 函数解释&#xff1a; 这些函数如果调用成功则…

【Web UI自动化】Python+Selenium 环境配置

安装Python 官网地址&#xff1a;https://www.python.org/&#xff0c;Downloads菜单下选择适合自己的系统版本&#xff0c;我的是Windows。 点击进入以后&#xff0c;可以看到当前最新版本。 点击上面的链接&#xff0c;页面下滑&#xff0c;找到下载链接&#xff0c;根据…

网站推荐——文本对比工具

在线文字对比工具-BeJSON.com 文本对比/字符串差异比较 - 在线工具 在线文本对比-文本内容差异比较-校对专用

OpenCV C++实现区域面积筛选以及统计区域个数

目录 1、背景介绍 2、代码实现 2.1 获取原图 2.1.1 区域图像imread 2.1.2 具体实现 2.2 获取图像大小 2.3 阈值分割 2.3.1 阈值分割threshold 2.3.2 具体实现 2.4 区域面积筛选 2.4.1 获取轮廓findContours 2.4.2 获取轮廓面积contourArea 2.4.3 填充区域fil…

PotatoPie 4.0 实验教程(28) —— FPGA实现sobel算子对摄像头图像进行边缘提取

什么是sobel算子&#xff1f; Sobel 算子是一种常用的边缘检测算子&#xff0c;用于在图像中检测边缘。它基于对图像进行梯度运算&#xff0c;可以帮助识别图像中灰度值变化较大的区域&#xff0c;从而找到图像中的边缘。 Sobel 算子通过计算图像的水平和垂直方向的一阶导数来…

探索数字化采购管理:构建高效智能的采购平台

随着信息技术的快速发展和普及&#xff0c;数字化采购管理正成为企业提升采购效率、降低成本、优化供应链的重要手段。本文将探讨数字化采购管理的规划设计&#xff0c;以帮助企业构建高效智能的采购平台&#xff0c;实现采购流程的数字化转型。 ### 1. 数字化采购管理的意义 …

【机器学习原理】决策树从原理到实践

基于树的模型是机器学习中非常重要的一类模型&#xff0c;最基础的就是决策树&#xff0c;本篇主要讲述决策树的原理和几类最常见的决策树算法&#xff0c;这也是更复杂的树模型算法的基础。 参考文章&#xff1a; 1.CSDN-基于熵的两个模型(ID3,C4.5)比较详细&#xff0c;有数字…

(超级详细)算法刷题Leecode15. 三数之和

题目描述 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组…

43. UE5 RPG 实现敌人血量显示条

在上一篇文章中&#xff0c;我们实现了火球术伤害功能&#xff0c;在火球击中敌方目标&#xff0c;可以降低敌人20的血量&#xff0c;这个值现在是固定的&#xff0c;后面我们会修改火球的伤害设置。接着&#xff0c;我们也测试了功能是实现的&#xff0c;但是在正常的游玩过程…

PotatoPie 4.0 实验教程(22) —— FPGA实现摄像头图像对数(log)变换

什么是图像的log变换&#xff1f; 总的来说&#xff0c;对数变换是一种常用的图像增强技术&#xff0c;可以改善图像的视觉质量、减少噪声以及突出图像中的细节&#xff0c;从而提高图像在视觉感知和分析中的效果和可用性。 图像的对数变换&#xff08;log transformation&am…

Canal入门使用

说明&#xff1a;canal [kə’nl]&#xff0c;译意为水道/管道/沟渠&#xff0c;主要用途是基于 MySQL 数据库增量日志解析&#xff0c;提供增量数据订阅和消费&#xff08;官方介绍&#xff09;。一言以蔽之&#xff0c;Canal是一款实现数据同步的组件。可以实现数据库之间、数…

【氮化镓】p-GaN HEMTs空穴陷阱低温冻结效应

这篇文章是关于低温条件下p-GaN高电子迁移率晶体管&#xff08;HEMTs&#xff09;栅极漏电的研究。文章通过电容深能级瞬态谱&#xff08;C-DLTS&#xff09;测试和理论模型分析&#xff0c;探讨了空穴陷阱对栅极漏电电流的影响。以下是对文章的总结&#xff1a; 摘要&#xf…

前端JS加密库CryptoJS的常用方法

CryptoJS是前端常用的一个加密库&#xff0c;如MD5、SHA256、AES等加密算法。 官方文档&#xff1a;https://www.npmjs.com/package/crypto-js 安装方法 方法一&#xff1a;直接在html文件中引入 <script type"text/javascript" src"path-to/bower_componen…

(六)几何平均数计算 补充案例 #统计学 #CDA学习打卡

一. 两个案例 1&#xff09;几何平均数计算&#xff1a;基金年平均增长率计算 在财务、投资和银行业的问题中&#xff0c;几何平均数的应用尤为常见&#xff0c;当你任何时候想确定过去几个连续时期的平均变化率时&#xff0c;都能应用几何平均数。其他通常的应用包括物种总体…

[Linux][网络][网络编程套接字][一][预备知识][套接字地址结构]详细讲解

目录 0.预备知识1.理解源IP地址和目的IP地址2.理解源MAC地址和目的MAC地址3.端口号4.理解端口号和进程ID5.理解源端口号和目的端口号6.通过IP地址、端口号、协议号进行通信识别7.认识TCP协议和UDP协议8.网络字节序 1.套接字地址结构(sockaddr) 0.预备知识 1.理解源IP地址和目的…

安装配置Maven(idea里面配置)

放在这个路径下&#xff08;如果需要可以免费发给你&#xff0c;dd我就好了&#xff09; D:\IearnSoftware\maven\apache-maven-3.6.1-bin.zip&#xff08;我自己的路径下面&#xff0c;防止忘记&#xff09; 1.首先测试maven在不在&#xff0c;配置对不对 mvn -v 这样就是成…

SpringMVC进阶(数据格式化以及数据校验)

文章目录 1.数据格式化1.基本介绍1.基本说明2.环境搭建 2.基本数据类型和字符串转换1.需求分析2.环境搭建1.data_valid.jsp首页面2.Monster.java封装请求信息3.MonsterHandler.java处理请求信息4.monster_addUI.jsp添加妖怪界面5.单元测试 3.保存妖怪信息1.MonsterHandler.java…
最新文章