【STL十七】函数对象:可变函数和参数——回调函数——如何取代虚函数

一、可变函数和参数

  • Args:可以作为一个整体传给bind,
  • 然后把可调用对象的实参一个个绑定,
  • 调用不同的函数对象。

可变参数也可以使用va_start、va_arg、va_end、va_list来实现,我们后面单独讲解。此处重点的bind的使用。

1、参数——拷贝语义

#include <iostream>
#include <thread>
#include <functional>        
using namespace std;

void show0() {
    
      // 普通函数。
	cout << "第0个,show0,长安归故里。\n";
}

void show1(const string& message) {
    
      // 普通函数。
	cout << "第1个,show1," << message << endl;
}

struct CC	// 类中有普通成员函数。
{
    
    
	void show2(int bh, const string& message) {
    
    
		cout << "第" << bh << "个," << message << endl;
	}
};

template<typename Fn, typename...Args>
auto show(Fn fn, Args...args)
{
    
    
	cout << "before show......\n";

	auto f = bind(fn, args...);

	cout << "after show。\n";
	return f;
}

int main()
{
    
    
	auto f0 = show(show0);
	f0();
	cout << endl;

	auto f1 = show(show1, "长安归故里。");
	f1();
	cout << endl;

	CC cc;
	auto f2 = show(&CC::show2, &cc, 2, "长安归故里。");
	f2();

	//thread t1(show0);
	//thread t2(show1,"我是一只傻傻鸟。");
	//CC cc;
	//thread t3(&CC::show2,&cc, 3,"我是一只傻傻鸟。");
	//t1.join();
	//t2.join();
	//t3.join();
	return 0;
}

输出

before show…
after show。
第0个,show0,长安归故里。


before show…
after show。
第1个,show1,长安归故里。


before show…
after show。
第2个,长安归故里。

2、参数——移动语义

#include <iostream>
#include <thread>
#include <functional>        
using namespace std;

void show0() {
    
      // 普通函数。
	cout << "第0个,show0,长安归故里。\n";
}

void show1(const string& message) {
    
      // 普通函数。
	cout << "第1个,show1," << message << endl;
}

struct CC	// 类中有普通成员函数。
{
    
    
	void show2(int bh, const string& message) {
    
    
		cout << "第" << bh << "个," << message << endl;
	}
};

template<typename Fn, typename...Args>
auto show(Fn&& fn, Args&&...args)// -> decltype(bind(forward<Fn>(fn), forward<Args>(args)...)) //c++11需要把此处放开,c++14不需要
{
    
    
	cout << "before show......\n";

	auto f = bind(forward<Fn>(fn), forward<Args>(args)...);

	cout << "after show。\n";
	return f;
}

int main()
{
    
    
	auto f0 = show(show0);
	f0();
	cout << endl;

	auto f1 = show(show1, "长安归故里。");
	f1();
	cout << endl;

	CC cc;
	auto f2 = show(&CC::show2, &cc, 2, "长安归故里。");
	f2();

	//thread t1(show0);
	//thread t2(show1,"我是一只傻傻鸟。");
	//CC cc;
	//thread t3(&CC::show2,&cc, 3,"我是一只傻傻鸟。");
	//t1.join();
	//t2.join();
	//t3.join();
	return 0;
}

输出

before show…
after show。
第0个,show0,长安归故里。


before show…
after show。
第1个,show1,长安归故里。


before show…
after show。
第2个,长安归故里。

3、std::forward

forward详细的,我们放到左值和右值时讲解

  • std::forward —— (C++11)转发一个函数实参
  • std::move —— (C++11)获得右值引用

二、回调函数

  • 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

我们仅仅讲解下把一个类的一个函数注册到另外一个类中,仅仅在有需要时调用。(其实以下代码完全可以使用虚函数来实现,比如NetClient提供虚函数的注册接口,class BB来负责虚函数的具体实现,在此我们不再扩展)

  • class NetClient:代表接受消息的客户端
  • class BB:代表接受客户端消息,处理业务的类
#include <iostream>
#include <string>
#include <thread>                      // 线程类头文件。
#include <functional>
using namespace std;

//void show(const string& message) {  // 处理业务的普通函数
//    cout << "处理数据:" << message << endl;
//}

class BB {
    
      // 处理业务的类
public:
    void show(const string& message) {
    
    
        cout << "处理业务的类:展示——" << message << endl;
    }
};

class NetClient
{
    
    
    
    function<void(const string&)> m_callback;  // 回调函数对象。
public:
    // 注册回调函数,回调函数只有一个参数(网络客户端接收到的数据)。
    template<typename Fn, typename ...Args>
    void register_callback(Fn&& fn, Args&&...args) {
    
    
        m_callback = bind(forward<Fn>(fn), forward<Args>(args)..., std::placeholders::_1);  // 绑定回调函数。
    }


    void receive() {
    
        // 消费者线程任务函数。
        while (true) {
    
    
            this_thread::sleep_for(chrono::seconds(2));    // 休眠2秒。

            // simulated revceive message from net server
            string message = "收到消息是:小明在濮阳工作。";

            // 处理出队的数据(把数据消费掉)。
            if (m_callback)
            {
    
    
                m_callback(message);  // 回调函数,把收到的数据传给它。
            }
        }
    }
};

int main()
{
    
    
    NetClient client;
    BB bb;
    client.register_callback(&BB::show, &bb);    // 把类成员函数BB::show()注册为回调函数。

    client.receive();
}

三、如何取代虚函数

C++虚函数在执行过程中会跳转两次(先查找对象的函数表,再次通过该函数表中的地址找到真正的执行地址),这样的话,CPU会跳转两次,而普通函数只跳转一次。
CPU每跳转一次,预取指令要作废很多,所以效率会很低。(百度)
为了管理的方便(基类指针可指向派生类对象和自动析构派生类),保留类之间的继承关系。

1、虚函数

#include <iostream>         // 包含头文件。
#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;     // 英雄的id。
	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;			// 释放派生类对象。
	}
}

输出

扫描二维码关注公众号,回复: 15271995 查看本文章

请输入英雄(1-西施;2-韩信。):1
西施释放了技能。

2、包装器和绑定器:取代虚函数

  • 包装器和绑定器不要求有继承关系
  • 如果有继承关系,基类释放会自动调用子类的析构函数,如果没有继承关系,则不会调用
#include <iostream>         // 包含头文件。
#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;     // 英雄的id。
	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;			// 释放派生类对象。
	}
}

输出

请输入英雄(1-西施;2-韩信。):1
西施释放了技能。

参考
1、C++ STL 容器库 中文文档
2、STL教程:C++ STL快速入门
3、https://www.apiref.com/cpp-zh/cpp/header.html
4、https://en.cppreference.com/w/cpp/container
5、WIKI教程_C ++标准库_C++ Library - <iterator>
6、哔哩哔哩_系统化学习C++_C++11神器之可调用对象包装器和绑定器

猜你喜欢

转载自blog.csdn.net/junxuezheng/article/details/129914851
今日推荐