参考原文地址:(原创) C++11 改进我们的模式之改进表驱动模式
我对文章的格式和错别字进行了调整,并标注出了重要的部分。以下是正文。
正文
所谓表驱动法(Table-Driven Approach),简单讲是指用查表的方法获取值。表驱动是将一些通过较为复杂逻辑语句来得到数据信息的方式,通过查询表的方式来实现,将数据信息存放在表里。对于消除长的 switch-case 和 if-else-if 语句来说很有效,比如下面的代码:
待优化的例子
string GetDayName(int day)
{
string dayName;
if (day == 1)
{
dayName = "星期一";
}
else if (day == 2)
{
dayName = "星期二";
}
else if (day == 3)
{
dayName = "星期三";
}
else if (day == 4)
{
dayName = "星期四";
}
else if (day == 5)
{
dayName = "星期五";
}
else if (day == 0)
{
dayName = "星期日";
}
}
这样的主要问题是:
- 代码太长,逻辑重复,复杂度高;
- 可维护性低,当新增一个流程分支时就要添加一个判断语句。
表驱动的改进
通过表驱动就一两条语句就可以代替上面长长的 if-else 语句。
string GetDayName(int day)
{
string dayNames[] = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
return dayNames[day]
}
这样不仅代码看起来简明,而且后面如果增加新的数值的话维护起来也更简单、方便。
表驱动的缺陷
表驱动法虽然对于消除长的 if-else 语句、提高代码质量很有用,但是一般的表驱动难以重用,即这个类的表驱动难以被其它类的表驱动所重用,因为其它类的表驱动逻辑和当前类的逻辑并不一定相同,具体来说是逻辑分支的函数不尽相同,这个问题导致表驱动模式的实现不够通用。
通用的表驱动模式
面临的问题
如果要做一个通用的表驱动模式就会面临两个问题:
-
第一个问题是如何在表中注册各种类型的执行函数,这些执行函数形参不尽相同,用 function 是不行的,因为 function 的类型在一开始就定下来了;
-
第二个问题是表找到了相应的执行函数之后调用该函数的问题,因为每个函数的形参不尽相同,如何以一种统一的方式去调用也是一个问题。
问题的解决
-
问题二比较好解决,用 C++11 的可变参数模板即可;
-
问题一需要通过类型擦除来解决,这要用到这个链接中实现的 Any 做这个事情。C++11 版本的表驱动模式,支持各种类型的 key ,执行函数支持普通函数、函数对象、lambda 表达式和成员函数。
#include <functional>
#include <type_traits>
template <typename Key>
class TableDriver
{
public:
template <typename... Args, typename Func>
void Register(Key &&key, Func &&func)
{
//typedef decltype(std::declval<Func>()(std::declval<Args>()...)) rettype;
typedef typename std::result_of<Func(Args...)>::type rettype;
auto f = std::function<rettype(Args && ...)>([=](Args &&...args)
{ return func(std::forward<Args>(args)...); });
m_map[key] = f;
}
template <typename R = void, typename... Args>
R Execute(Key &&key, Args &&...args)
{
auto it = m_map.find(key);
if (it == m_map.end())
return (R) nullptr;
auto f = it->second.AnyCast<std::function<R(Args && ...)>>();
return f(std::forward<Args>(args)...);
}
//不带参数的执行
template <typename R>
R Execute(Key &&key)
{
auto it = m_map.find(key);
if (it == m_map.end())
return (R) nullptr;
auto f = it->second.AnyCast<std::function<void()>>();
return f();
}
private:
std::map<Key, Any> m_map;
};
测试代码
struct MyStruct2
{
void g(int x)
{
cout << x << endl;
}
void g1()
{
cout << 2 << endl;
}
};
struct MyStruct3
{
void operator()(int x) const
{
cout << x + 1 << endl;
}
};
void TestTbDriver()
{
TableDriver<int> dv;
auto f = [](int a, int b)
{ cout << a + b << endl; };
auto fuc = [](int a)
{ cout << a << endl; };
auto fuc1 = []()
{ cout << 1 << endl; };
dv.Register<decltype(f), int, int>(1, f); //带参数的 lambda 表达式
dv.Register<decltype(fuc), int>(2, fuc);
dv.SimpleRegister<decltype(fuc1)>(22, fuc1); //不带参数的 lambda 表达式
MyStruct2 st2;
dv.Register<int>(4, &st2, &MyStruct2::g); //带参数的成员函数
MyStruct3 st3;
dv.Register<decltype(st3), int>(11, st3); //函数对象
dv.SimpleRegister(44, &st2, &MyStruct2::g1); //不带参数的成员函数
int a = 3, b = 4;
dv.Execute(2, a);
dv.Execute(1, a, b);
dv.Execute(22);
dv.Execute(44);
dv.Execute(11, a);
}
测试结果
原作者的更新
做一个更新,对代码进行简化和优化,更少的代码,并且可以带返回值了
代码中用到的 Any 就是博客 中的 Any
#include "Any.hpp"
template <typename Key>
class TableDriver
{
public:
template <typename... Args, typename Func>
void Register(Key &&key, Func &&func)
{ //typedef decltype(std::declval<Func>()(std::declval<Args>()...)) rettype; typedef typename std::result_of<Func(Args...)>::type rettype; auto f = std::function<rettype(Args && ...)>([=](Args && ... args){return func(std::forward<Args>(args)...); }); m_map[key] = f; }
template <typename R = void, typename... Args>
R Execute(Key && key, Args && ...args)
{
auto it = m_map.find(key);
if (it == m_map.end())
return (R) nullptr;
auto f = it->second.AnyCast<std::function<R(Args && ...)>>();
return f(std::forward<Args>(args)...);
}
//不带参数的执行 template <typename R> R Execute(Key && key) { auto it = m_map.find(key); if (it == m_map.end()) return (R)nullptr;
auto f = it->second.AnyCast<std::function<void()>>();
return f();
}
private:
std::map<Key, Any> m_map;
};
测试代码:
struct Tdd
{
int Test(int x) { return x + 2; }
};
void TestDriver()
{
TableDriver<string> dv;
dv.Register("aa", []
{ cout << "aa test;" << endl; });
dv.Execute("aa");
int y = 0;
dv.Register<int, int>("aa", [](int a, int b)
{ return a + b; });
auto t = dv.Execute<decltype(y)>("aa", 3, 4);
Tdd a;
dv.Register<int>("aa", [&a](int x)
{ return a.Test(x); });
auto t1 = dv.Execute<int>("aa", 3);
dv.Register<string>("aa", [](string x)
{ return x; });
auto t2 = dv.Execute<string>(std::move(string("aa")), std::move(string("test")));
}