【问题标题】:How do I create a clean cascading if structure in c++?如何在 C++ 中创建干净的级联结构?
【发布时间】:2013-05-24 05:09:51
【问题描述】:

我正在使用 boost 的正则表达式库,我发现确定是否找到命名匹配然后使用该信息有点烦人。要检测命名匹配,我想这样做:

typedef boost::match_result<string::const_iterator> matches_t;
typedef matches_t::const_reference match_t;
boost::regex re("(?:(?<type1>aaaa)|(?<type2>bbbb)" /*...*/ "|(?<typeN>abcdefg)");
string str(SOME_STRING);
matches_t what;
boost::match_flag_type flags = boost::match_default;

if(regex_search(str.cbegin(), str.cend(), what, re, flags))
{
  if((match_t type1 = what["type1"]).matched)
  {
     // do stuff with type1
  }
  else if((match_t type2 = what["type2"]).matched)
  {
     // do stuff with type2
  }
  // ...
  else if((match_t typeN = what["typeN"]).matched)
  {
     // do stuff with typeN
  }
}

如果那会奏效,那就太好了。范围将被限制在 if 的主体中,内存可以被有效地使用并且看起来相当干净。遗憾的是,它不起作用,因为您无法在列表中定义变量。 :(

这有可能:

if(regex_search(str.cbegin(), str.cend(), what, re, flags))
{
  match_t found = what["type1"];
  if(found.matched)
  {
     // do stuff with type1
  }
  else if((found = what["type2"]).matched)
  {
     // do stuff with type2
  }
  // ...
  else if((found = what["typeN"]).matched)
  {
     // do stuff with typeN
  }
}

但是 match_t 是一个 const 引用,所以它是不可赋值的。 (tl;dr 另外我不知道底层类型是什么,通常我也不想知道,因为我更喜欢一种更通用的解决方案,我可以在这个正则表达式示例之外使用它。甚至std::move() 用于围绕什么 [...] 它变得更加冗长,并且文档没有说它对 sub_match 使用移动语义。由于第一个给出的原因,所有这些当然都是没有意义的本段的句子。)

另一种选择是这样做:

if(regex_search(str.cbegin(), str.cend(), what, re, flags))
{
  match_t type1 = what["type1"];
  if(type1.matched)
  {
     // do stuff with type1
  }
  else {
    match_t type2 = what["type2"];
    if(type2.matched)
    {
       // do stuff with type2
    }
    // ...
          else {
            match_t typeN = what["typeN"];
            if((match_t typeN = what["typeN"]).matched)
            {
               // do stuff with typeN
            }
          }
    // ...
    }
  }
}

由于大括号嵌套太深,我不喜欢。

可能会在每个 if 正文的末尾滥用带有 breaks 的循环结构,如下所示:

if(regex_search(str.cbegin(), str.cend(), what, re, flags))
{
  do{
    {
      match_t type1 = what["type1"];
      if(type1.matched)
      {
         // do stuff with type1
         break;
      }
    }
    {
      match_t type2 = what["type2"];
      if(type2.matched)
      {
         // do stuff with type2
         break;
      }
    }
    // ...
    {
      match_t typeN = what["typeN"];
      if(typeN.matched)
      {
         // do stuff with typeN
         break;
      }
    }
  } while(0);
}

哪个更好,但仍然不是很好。使用宏,可以隐藏大部分噪音。喜欢:

#define IF(declare, cond) do{{declare;if(cond){                
#define ELSE_IF(declare, cond) break;}}{declare; if(cond){     
#define ELSE break;}}{{                                        
#define END_IF break;}}}while(0);                              

if(regex_search(str.cbegin(), str.cend(), what, re, flags))
{
  IF(match_t type1 = what["type1"], type1.matched)
  {
     // do stuff with type1
  }
  ELSE_IF(match_t type2 = what["type2"], type2.matched)
  {
     // do stuff with type2
  }
    // ...
  ELSE_IF(match_t typeN = what["typeN"], typeN.matched)
  {
     // do stuff with typeN
  }
  END_IF
}

大括号实际上是由宏所隐含的,但通过重述它们可以使阅读更清晰。

我能想到的另一个选择是进入 boost::sub_match 类并添加一个转换函数将该类型转换为返回值将是 matched 成员的布尔值。然后我可以在 if 表达式中声明一个 match_t 变量,它会被 if 自动转换为布尔值。我不确定我是否还在那里,它不是通用的。

在风格上,我建议的那些是好是坏(只有最后 3 个实际上有效,所以我可能会将 cmets 限制在它们身上)。

另外,有没有人有更好的建议?请说明您认为它们更好的原因。

【问题讨论】:

  • 请注意,if(foo = bar) 与 C++ 中的 if(foo == bar) 不同,代码可能不会执行您想要的操作。此外,我强烈建议不要像上一个示例那样使用宏来“删除垃圾”。它不会删除“垃圾”,但会使代码难以理解。
  • @Damon:是的,我知道 = 与 == 不同。该代码完全符合我的要求,以限制范围、减少噪音和重用堆栈空间(如果编译器对其进行了优化)。我有点同意你的观点:宏,但只要你知道宏的用途,它确实可以减少多余的噪音,这可以让事情变得更清晰。但是YMMV。所以我认为你更喜欢带有do { } while(0); 没有宏的第二个?
  • 不确定,我可能会编写第一个版本(在外部if 范围内声明变量,因此它可以编译),因为在我看来,这是最不容易混淆的版本。但归根结底,这取决于个人品味。而且,我会在 if 内的分配周围加上双括号,以明确表明这些不是偶然的(启用适当的警告,这也是 GCC 建议做的)。
  • @Damon:第一个?还是5个中的第3个?我问这个是因为第一个不起作用,并且不可能预先声明变量而不给它们赋值,因为类型是 const 引用。
  • if(regex_search(...)) 包围的块中声明(并分配,因为它们是引用)三个作为第一件事的障碍是什么?这应该可以正常工作,不是吗?在最坏的情况下,它会徒劳地初始化 2 个变量,但平均而言它会很好,而且它是可读的代码:)

标签: c++ coding-style


【解决方案1】:

通常建议避免嵌套ifs - 它们使代码更难阅读。如果有嵌套的 if,它可能应该被函数调用替换。

在您的情况下,您需要使用循环。

你的第二个例子:

if(regex_search(str.cbegin(), str.cend(), what, re, flags))
{
  match_t found = what["type1"];
  if(found.matched)
  {
     // do stuff with type1
  }
  else if((found = what["type2"]).matched)
  {
     // do stuff with type2
  }
  // ...
  else if((found = what["typeN"]).matched)
  {
     // do stuff with typeN
  }
}

BEGS 循环:

const char *types[] = {"type1", "type2", "typeN", 0};
for(const char **cur = types; *cur; cur++){
    found = what[*cur];
    if (found.matched){
         //initiate robot uprising
         break;
    }
}

您的所有其他示例 (IMO) 都是糟糕的编码风格。我更喜欢保持循环和 ifs 简短。如果它不适合 20 行代码,那么最好做一些非常复杂的事情(这不是你的情况)。如果它没有做任何复杂的事情,则需要对其进行重组。

【讨论】:

  • 我绝对喜欢这个,因为它易于扩展,干净且易于使用函数指针跳转表。我认为我们有一个赢家! :) 虽然 IMO 应该使用标准迭代器样式,而不是在数组末尾使用 NULL。
  • 哦,你的语法太差了。
  • 阿德里安:“语法离题了”?你的意思是“{在行尾”的东西还是别的什么?
  • 你知道吗,它可能不是。 C++11 中有很多语法变化。我刚刚升级到 C++11,根据我从 C++ 中了解到的情况,您使用的指针初始化无效。这是 C++11 的新功能吗?我更习惯const char *types[] = {"type1", "type2", "typeN", 0};
  • @Adrian:哎呀。那是一个错字(我在没有编译的情况下发布了代码)。事实上,他们两个。很好地注意到了这个问题。顺便说一句,我目前使用 C++03 标准。
【解决方案2】:

您可以这样做(请注意,此代码未针对编译器进行测试)

// create a map between match types and actions
std::map<std::string, std::function<match_t>> actions;
actions["type1"] = [&](match_t type) {...};

// iterate through the actions map and check each match type
for(auto kvp : actions)
{
   match_t type = what[kvp.first];
   if(type.matched)
   {
      // do stuff with this result
      kvp.second(type);
   }
}

【讨论】:

  • 嗯,很有趣。但是,您忘记了 if 身体末尾的休息时间。其他潜在问题是 1. 您无法定义首先测试哪个(尽管这可以使用元组的向量来完成)。 2. 将代码从执行点移开。但是使用这样的 lambda 绝对是有趣的。也可以使用完整的功能来完成。 +1 独创性。 :)
  • 人们对本文的可读性有何看法?一个动作应该相当短。不超过 10 行。
  • 您可以将地图替换为列表或其他序列容器,以便控制匹配评估的顺序。
  • 是的,我在第一条评论中确实提到了这一点。尽管我将其表示为向量而不是通用序列容器。
【解决方案3】:

您可以编写 match_t 的包装,并带有 operator bool 的重载:

struct bmatch
{
    matches_t::const_reference ref;
    bmatch(matches_t::const_reference r)
    :ref(r)
    {}

    operator bool() const
    {
        return ref.matched;
    }
};

然后:

if (bmatch type1 = what["type1"])
{ //Use type2.ref
}
else if (bmatch type2 = what["type2"])
{ //Use type2.ref
}

你也可以重载operator-&gt;

    matches_t::const_reference operator->() const
    {
        return ref;
    }

【讨论】:

  • IMO,像 if (bmatch type1 = what["type1"]) 这样的语句可读性不强,我宁愿避免使用它们。
猜你喜欢
  • 2014-01-10
  • 2022-05-02
  • 2011-08-28
  • 1970-01-01
  • 1970-01-01
  • 2018-11-15
  • 2013-01-16
  • 2021-12-22
  • 1970-01-01
相关资源
最近更新 更多