前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++11--使用表驱动(Table-Driven)模式消除if-else和switch-case语句

C++11--使用表驱动(Table-Driven)模式消除if-else和switch-case语句

作者头像
YoungTimes
发布2022-04-28 15:44:39
1.6K1
发布2022-04-28 15:44:39
举报

在日常的代码编写中经常出现不同数据格式转换的场景,比如给定一个整数,将其与每周的星期名称映射起来,该如何实现呢?

常见的实现途径是通过if-else或者switch-case的方式来实现,如下代码所示:

代码语言:javascript
复制
const std::string GetDayName(const int day) {
  std::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 == 6) {
    dayName = "星期六";
  } else if(day==0) {
    dayName = "星期日";
  }
  return dayName;
}

这样的代码优势是简单,初学者也可以写出这样的代码;代码的问题在于: 1) 代码太长,逻辑重复冗余,复杂度高; 2) 可维护性低,耦合性强,每新增一个流程分支时就要在函数代码中添加一个判断语句。

1、简单的表驱动实现

如何解决写出更加优雅的代码来消除if-else/switch-case语句,表驱动法(Table-Driven Approach)是一种可选的方法。表驱动的方法是指把数据信息放置表中,通过查表的方法获取数值的方法。表驱动的代码如下:

代码语言:javascript
复制
#include <cassert>
#include <string>

const std::array<std::string, 7> dayNames = {"星期日","星期一","星期二","星期三","星期四","星期五","星期六"};

const std::string GetDayName(const int day) {
    assert(day >= 0);
    assert(day <= 6);
    return dayNames[day];
}

通过几行代码就可以替代冗长的if-else代码,使得代码不仅简单明了,而且也方便代码维护。

上述表驱动方法虽然对于消除长的if-else语句、提高代码质量很有用,但是一般的表驱动难以重用。因为不同的业务有不同的场景,不同的逻辑分支,这些都导致上述的表驱动的方式实现不够通用。

2、一种通用的表驱动实现

实现一个通用的表驱动模式(Table-Driven Approach)需要解决两个问题:

1)如何在表中注册不同类型的执行函数。也许有人会说是不是可以采用C++的函数包装器std::function,但是在实际应用中,执行函数的形参不尽相同,但std::function的参数类型在一开始就确定了。

2)在表中找到对应的执行函数之后如何调用执行函数。由于每个函数的形参不尽相同,如何以统一的方式调用也是一个问题;

问题2)可以采用C++ 11的可变模板参数解决;问题1)需要使用C++ boost::Any来解决。通用的表驱动C++11实现支持各种类型的key,执行函数支持普通函数、函数对象、lamda表达式和成员函数。

代码语言:javascript
复制
#include <iostream>
#include <array>
#include <map>
#include <functional>
#include <type_traits>
#include <boost/any.hpp>
#include <typeinfo>

template <typename Key>
class TableDriver {
public:
  template <typename... Args, typename Func>
  void Register(const Key &key, Func &&func) {
    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(const Key &key, Args &&... args) {
    auto it = m_map.find(key);
    if (it == m_map.end())
      return R();

    auto f = boost::any_cast<std::function<R(Args && ...)>>(it->second);
    return f(std::forward<Args>(args)...);
  }

  template <typename R = void> R Execute(const Key &key) {
    auto it = m_map.find(key);
    if (it == m_map.end())
      return R();
    
    auto f = boost::any_cast<std::function<R()>>(it->second);
    return f();
  }

private:
  std::map<Key, boost::any> m_map;
};

测试代码:

代码语言:javascript
复制
struct Tdd {
  int Test(int x) {return x + 2;}
};

int main() {
  TableDriver<std::string> dv;
  dv.Register("aa", []{std::cout << "aa test;" << std::endl;});
  dv.Execute("aa");

  int y = 0;
  dv.Register<int, int>("aa", [](const int& a, const int& b) {return a + b;});
  auto t = dv.Execute<decltype(y)>("aa", 3, 4);
  std::cout << t << std::endl;

  Tdd a;
  dv.Register<int>("aa", [&a](int x) {return a.Test(x);});
  auto t1 = dv.Execute<int>("aa", 3);
  std::cout << t1 << std::endl;

  dv.Register<std::string>("aa", [](const std::string& x) {return x;});
  auto t2 = dv.Execute<std::string>("aa", std::string("test"));
  std::cout << t2 << std::endl;
    
  return 0;
}

函数执行结果:

代码语言:javascript
复制
aa test;
7
5
test

然后我们可以重写开头的格式转换代码:

代码语言:javascript
复制
TableDriver<int> dv;

const std::string GetDayName(const int& day) {
    return dv.Execute<std::string>(day);
}

int main() {
  dv.Register(0, []()->std::string{return "星期日";});
  dv.Register(1, []()->std::string{return "星期一";});
  dv.Register(2, []()->std::string{return "星期二";});
  dv.Register(3, []()->std::string{return "星期三";});
  dv.Register(4, []()->std::string{return "星期四";});
  dv.Register(5, []()->std::string{return "星期五";});
  dv.Register(6, []()->std::string{return "星期六";});
    
  int day = 3;
  std::cout << GetDayName(day) << std::endl;
  return 0;
}

参考链接

https://www.cnblogs.com/qicosmos/p/3146402.html

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 半杯茶的小酒杯 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、简单的表驱动实现
  • 2、一种通用的表驱动实现
  • 参考链接
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档