C++用unordered_map查表代替if else/switch case多判断语句

Source

一、引言

在C++中我们写判断逻辑一般会用if else或switch case语句,比如以下例子:

#include <iostream>

using namespace std;


class CTest
{
public:
	enum class ConditionType
	{
		TYPE1 = 0,
		TYPE2,
		TYPE3,
	};
	CTest() = default;
	~CTest() = default;
	void execFun(ConditionType eConditionType) //根据条件执行对应函数
	{
		switch (eConditionType) {
		case ConditionType::TYPE1: {
			func1();
			break;
		}
		case ConditionType::TYPE2: {
			func2();
			break;
		}
		case ConditionType::TYPE3: {
			func3();
			break;
		}
		default:break;
		}
	}
	void execAllFun() //执行所有函数
	{
		func1();
		func2();
		func3();
	}
	void func1() { cout << "Func1" << endl; }
	void func2() { cout << "Func2" << endl; }
	void func3() { cout << "Func3" << endl; }
};

CTest gTest;

int main()
{
	CTest::ConditionType eConditionType = CTest::ConditionType::TYPE1;
	gTest.execFun(eConditionType);
	gTest.execAllFun();
}

执行效果如下:

 上面的例子很简单,我们用了switch case实现。但有如下不足:

1、swtich语句无法对字符串进行判断,只能对整形/枚举进行判断。

2、代码可读性差。上面的例子因为只有3个判断分支,所以阅读起来还好,但如果有成百上千个判断分支,那execFun函数和execAllFun函数里面的代码会变得非常臃肿,难以阅读。

3、代码可拓展性差。每新增一个分支都要修改execFun函数和execAllFun函数。

二、使用unordered_map查表代替if else和switch case语句

基于上述if else和switch case语句的不足我们可以使用unordered_map查表的方法来重构代码。

#include <iostream>
#include <unordered_map>
#include <functional>


using namespace std;


class CTest
{
public:
	enum class ConditionType
	{
		TYPE1 = 0,
		TYPE2,
		TYPE3,
	};
	CTest()
	{
		m_map[ConditionType::TYPE1] = bind(&CTest::func1, this);
		m_map[ConditionType::TYPE2] = bind(&CTest::func2, this);
		m_map[ConditionType::TYPE3] = bind(&CTest::func3, this);
	}
	~CTest() = default;
	void execFun(ConditionType eConditionType)   //根据条件执行对应函数
	{
		auto it = m_map.find(eConditionType);
		if (it != m_map.end())
		{
			it->second();
		}
	}
	void execAllFun() //执行所有函数
	{
		for (auto const& e : m_map)
		{
			e.second();
		}
	}
	void func1() { cout << "Func1" << endl; }
	void func2() { cout << "Func2" << endl; }
	void func3() { cout << "Func3" << endl; }
private:
	unordered_map<ConditionType, function<void()>> m_map;
};

CTest gTest;

int main()
{
	CTest::ConditionType eConditionType = CTest::ConditionType::TYPE1;
	gTest.execFun(eConditionType);
	gTest.execAllFun();
}

上述代码中使用了std::unordered_map而不是std::map是因为unordered_map查询速度更快,具体可以参考《map和unordered_map的区别

执行效果如下:

可以看到执行效果跟使用switch case是一摸一样的。并且有如下优点:

1、unordered_map容器类的key可以保存任意类型的数据。所以其实现的判断语句既可以对字符串进行判断,也能对整形/枚举进行判断。

2、代码拓展性强。新增分支不需要修改execFun函数和execAllFun函数,只需要改CTest类的构造函数这一个地方。

3、代码可读性强。直接在CTest类的构造函数中看unordered_map的key 关联了哪个value就可以知道整个判断逻辑。

三、进一步优化:使用单例模式

unordered_map由于建立了哈希表,所以它在最开始建立的时候比较耗时间。在实际的工程项目中我们可以把CTest类设计成单例,让unordered_map只被初始化一次。设计成单例模式的另外一个优点是把要经常使用的资源(全局变量、判断逻辑等)都放到一个地方,这样无论在代码的哪个类里面都能很方便地获取使用这些资源。

#include <iostream>
#include <unordered_map>
#include <functional>

using namespace std;


template<typename T>
class Singleton
{
public:
	static T& GetInstance()
	{
		static T instance;
		return instance;
	}

	Singleton(T&&) = delete;
	Singleton(const T&) = delete;
	void operator= (const T&) = delete;

protected:
	Singleton() = default;
	virtual ~Singleton() = default;
};

#define CT CTest::GetInstance()
class CTest : public Singleton<CTest>
{
public:
	enum class ConditionType
	{
		TYPE1 = 0,
		TYPE2,
		TYPE3,
	};
	CTest()
	{
		m_map[ConditionType::TYPE1] = bind(&CTest::func1, this);
		m_map[ConditionType::TYPE2] = bind(&CTest::func2, this);
		m_map[ConditionType::TYPE3] = bind(&CTest::func3, this);
	}
	~CTest() = default;
	void execFun(ConditionType eConditionType) //根据条件执行对应函数
	{
		auto it = m_map.find(eConditionType);
		if (it != m_map.end())
		{
			it->second();
		}
	}
	void execAllFun() //执行所有函数
	{
		for (auto const& e : m_map)
		{
			e.second();
		}
	}
	void func1() { cout << "Func1" << endl; }
	void func2() { cout << "Func2" << endl; }
	void func3() { cout << "Func3" << endl; }
private:
	unordered_map<ConditionType, function<void()>> m_map;
};


int main()
{
	CTest::ConditionType eConditionType = CTest::ConditionType::TYPE1;
	CT.execFun(eConditionType);
	CT.execAllFun();
	return 0;
}

执行效果如下:

四、总结

 使用unordered_map查表操作代替if else/switch case语句,适用于判断分支非常多的场合。比如我们设计平台服务器,该服务器需要跟数十种不同类型的客户端/设备进行通信,涉及成百上千条通信指令。平台服务器接收通信指令后需要执行对应的回调函数,这个时候我们可以用unordered_map查表代替if else/switch case语句,来提高代码的可读性和维护性。

五、参考

if-else VS map lookup

C++ 单例模式的模板实现