跟我学C++中级篇——处理if条件分支语句

225 篇文章 94 订阅

一、问题

对开发者而言,针对不同情况下的处理,最常用的肯定是if…else条件分支语句。正常情况下,两三个的条件判断或者说不影响到性能的分支处理在实际工作中还是占大部分的应用场景的。换句话说,对条件语句的处理其实并没有想象的那么迫切。包括很多网上的高手们反复强调其的效率低下等情况,其实普遍性的意义并不多大。
但凡事都有例外,少数场景下不代表这些场景不常见或者说少见。所以经常可以在网上看到有人吐槽别人写的庞大的if…else语句。而且最可怕的是条件语句里嵌套条件语句,有的甚至有十层以上。一般建议条件分支语句的嵌套不要超过两层,因为太深的嵌套会导致逻辑的不可控和维护的可读性,而太多的条件判断更是显得代码零乱不好维护。同时,大量使用条件分支语句来处理一些分支语句会导致CPU分支预测的失败,导致指令流水的重排,也就是性能的大幅降低。
所以大量使用if语句条件判断(含嵌套)会导致两个主要问题:
1、可读性差导致的可维护性差
2、分支预测几乎失效,导致性能降低

二、解决的三个层次

看到了if条件分支语句的问题,那么就可以从几个维度来解决它。根本的目的只有两个,解决可读性可维护性以及减少甚至放弃分支预测。下面从三个维度上进行说明:
1、设计维度
有些条件语句其实是对业务逻辑的一种响应,那么可以使用一些如工厂模式或者策略模式等来实现利用不同的类对象自动选择可执行的操作。对于一些简单的条件处理,还可以使用继承的方式,通过虚函数的动态绑定来实现取代条件语句。
但单纯的使用虚函数的迟后联编可能会比较好的解决可维护性问题,但对性能可能仍然会有一些损失。所以就需要根据实际情况来确定是否采用这种方式。

2、技术维度
如果只是单纯的使用技术解决if语句,也有不少办法。最简单的是使用switch…case,它在条件大于五个时,明显效率要高于if语句,但缺点是多了仍然会不好维护。那么可以使用位或表处理,它们本质基本一样,都是对固定的一个表(位其实可以理解成更小的表)来查询。但这种操作一般在C语言中比较多。在C++中可以使用映射(map,unordered_map),通过对业务逻辑和Key的映射来动态实现分支的选择。

3、编译维度
在学习模板编程和C++17一些新特性时,发现可以在编译期将分支直接固定,而勿需在程序运行时再进行判断。所以完全可以借助如if constexpr和模板等手段来实现编译期的分支确定,从而避免性能的损失。但这两个也有一个缺点,那就是可能可读性仍然无法有较大的改观。如同前面的“设计维度”一样,需要综合实地情况进行选择使用。

三、例程

1、使用策略模式


#include <iostream>
#include <memory>

class Action1 {
public:
  int action() { std::cerr << "Action1" << std::endl; }
};
class Action2 {
public:
  int action() { std::cerr << "Action2" << std::endl; }
};
template <class ActionType> class StrategyAction {
public:
  StrategyAction(std::shared_ptr<ActionType> action) : m_action(action) {}

  ~StrategyAction() {}

  int run() { return m_action->action(); }

private:
  std::shared_ptr<ActionType> m_action;
};

int main() {
  std::shared_ptr<Action1> a1;
  std::shared_ptr<Action2> a2;

  StrategyAction sa1(a1);
  sa1.run();
  StrategyAction sa2(a2);
  sa2.run();

  return 0;
}

2、使用映射

 using CmdFunc = std::function<int32_t(const int32_t &, const std::string &)>;
 std::unordered_map<int, CmdFunc> m_msgProcessDispatchMap;

 int32_t test1(const int32_t&t,const std::string &v){
 std::cerr<<"call test1!"<<std::endl;
 return 0;
 }
 int32_t test2(const int32_t&t,const std::string &v){
 std::cerr<<"call test2!"<<std::endl;
 return 0;
 }
 int32_t test3(const int32_t&t,const std::string &v){
 std::cerr<<"call test3!"<<std::endl;
 return 0;
 }

 void initCmdMap() {
  this->m_msgProcessDispatchMap.emplace(1,std::bind(&test1, this, std::placeholders::_1,std::placeholders::_2));
  this->m_msgProcessDispatchMap.emplace(2,std::bind(&test2, this, std::placeholders::_1,std::placeholders::_2));
  this->m_msgProcessDispatchMap.emplace(3,std::bind(&test3, this, std::placeholders::_1,std::placeholders::_2));
}
 void cmdDispatch(const int32_t &t,  const std::string &v) {
  if (this->m_msgProcessDispatchMap.count(t) > 0) {
    auto processFn = this->m_msgProcessDispatchMap[t];
    processFn(t,  v);
  } else {
    std::cerr<< "cmdDispatch:do not found process func!"<<std::endl;
  }
}
int main()
{
  initCmdMap();
  cmdDispatch(1,"test");

  return 0;
}

3、使用if constexpr

#include <variant>
#include <iostream>

std::variant<std::monostate, int, double, std::string> vt = 100;

void varConstExpr(const std::variant<std::monostate, int, double, std::string>& v) {
    std::visit([](auto&& parms)
        using T = std::decay_t<decltype(parms)>;
        if constexpr (std::is_same_v<T, int>) {
            std::cerr << "int: " << parms << std::endl;
        } else if constexpr (std::is_same_v<T, double>) {
            std::cerr << "double: " << parms << std::endl;
        } else if constexpr (std::is_same_v<T, std::string>) {
            std::cerr << "string: " << parms << std::endl;
        } else {
            std::cerr << "empty" << std::endl;
        }
    }, vt);
}

对于使用其它如模板等的例程在前面都有过体现,这里就不一一列举了,有兴趣可以自己写一写。另外,如果使用反射则可以更好的解决这类问题,但C++使用反射本身会引入更复杂的技术,所以这个需要自己来决策是否应用 。

四、总结

仍然是那句老话,“没有包打天下的技术”。想要灵活机变的综合应用各种技术解决实际问题,首先应该把基础打好,然后在实践中不断的应用总结。要善于发扬每种技术的长处,共同配合完成工作需要。所以搞技术学一些系统工程的知识也是相当有用处的。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值