"Abusing"循环,用于减少中频嵌套
"Abusing" loops for reducing if-nesting
有时必须实现一系列if/else检查。在过去,goto是这方面的标准工具。
由于goto在许多代码风格指南中是不允许的,因此我有时使用循环作为替代,例如:
do
{
if (a)
{
doA();
break;
}
//...
if (z)
{
doZ();
break;
}
}while(false);
//all break jump here
这是一个好方法吗?是否有一个好的c++模式(例如使用模板,继承等)来实现这一点,而不需要太多的开销?
由于条件似乎不相关,else if
是一个选项:
if (a) {
doA();
} else if (b) {
//...
} else if (z) {
doZ();
}
一般来说,为了编写可维护的代码,您应该按照预期的方式使用该语言的工具。您使用的循环应该只循环一次迭代,这显然是一种偏转,而不是由一开始就不赞成goto
的同一组代码风格指南所打算的。
语言中有一些可用的工具,使您更容易与其他程序员和您的进一步自我交流您打算在这里做什么。
- 其中之一是将该代码块外包到一个单独的方法中,并使用
- 另一个解决方案可能是使用异常,在那里你尝试/catch。它是否符合你的目的主要取决于你正在做的"检查"类型。 最后,if/else链可能看起来不太优雅,但另一方面,这是一个几乎没有误解空间的结构。
return
语句。有趣的是,一些人也不赞成提前退货。使用goto
,使用continue
,使用多个break
,或使用多个return
都是,如果滥用,不同口味的意大利面条编码,所有这些都可以用来创建非条件分支。一些滥用的例子:
-
goto
被认为非常糟糕,如果用于除向下跳转以外的任何其他用途,例如跳转到错误处理程序。(虽然这可能是goto
的一种可接受的用法,但它仍然引发了一场"打死马"的争论,所以我避免使用goto
。) -
continue
无条件向上跳跃,这被认为是不好的:意大利面条代码的一个定义是代码无条件向上和向下跳跃。当多个continue
被添加到同一个循环中时,情况尤其糟糕。一般来说,continue
的存在是一个非常确定的循环标志,需要重写。 -
多个
break
和多个return
可以被滥用来脱离复杂的嵌套循环,使代码难以阅读和维护。此外,问题中演示的break
方法强制使用有点晦涩的do-while(false)循环。
我相信多个returns
是最易读的形式,因为它可以与一些函数结果变量混合在一起,您可能无论如何都想要:
result_t func ()
{
if (a)
{
doA();
return RESULT_A;
}
... // you'll need lots of if statements here to justify this program design
if (z)
{
doZ();
return RESULT_Z;
}
return RESULT_NORMAL;
}
关于函数内部资源分配的边注。
如果上面的函数需要释放一些已分配的资源,并且它总是需要这样做,那么应该使用RAII这样做,这样当局部变量超出作用域时,它就会自己清理。
如果它只需要在某些情况下(例如在错误时)释放一些已分配的资源,那么您不应该在每个If语句中这样做(不可维护),也不应该实现一些80年代BASIC程序员的"在错误时goto"。相反,可以考虑在main函数之外添加一个包装器函数,以使程序易于维护并最小化混乱:
result_t wrapper ()
{
stuff = allocate(); // if needed
result_t result = func(stuff);
if(result == BAD)
{
deallocate(stuff);
}
return result;
}
您的非循环对您的程序员同事来说有点滥用,但并不可怕。您可以在断言宏和日志记录器的实现中看到很多这样的代码。
当然,有了模板,滥用的机会呈几何级数地扩大。例如,你可以这样写:
void doA() {
std::cout << "A" << std::endl;
}
void doB() {
std::cout << "B" << std::endl;
}
void either_or() {}
template<class T>
void either_or(T&& t) {
if (std::get<bool>(t)) {
std::get<void(*)()>(t)();
}
}
template<class T, class...Ts>
void either_or(T&& t, Ts&&... ts)
{
if (std::get<bool>(t)) {
std::get<void(*)()>(t)();
}
else {
either_or(std::forward<Ts>(ts)...);
}
}
auto main() -> int
{
using namespace std;
bool a = false;
bool b = true;
either_or(make_tuple(a, &doA),
make_tuple(b, &doB));
return 0;
}
这虽然是执行第一个动作的有效方法(在优化器干预之后),对应的标志为真,但对于维护者来说,这是一种更滥用的方法。
恕我直言,我不认为使用goto
有任何错误。如果您知道如何、在哪里以及为什么使用goto
,那么您的代码将更加清晰易读。
例如,Linux使用了大量的正如@Bartek Banachewicz所说,"Linux代码有gotos并不意味着它们不那么糟糕。"goto
语句。
goto
并不总是坏的,它们有很多有效的用途,比如打破深嵌套循环。在这种情况下,使用goto
将呈现更清晰的代码。只有当它被滥用或使用在不像c++程序那样的地方时,才会导致面条式代码。
注意:不必要使用任何循环,if-else语句,break, continue如果不这样做,将导致代码混乱和难以管理。即使我们让这个世界成为一个
un-goto
的地方,意大利面条代码将存在。这是这取决于个人,以确保他/她写一个干净,可读,优化代码。
最好是在c++程序中使用exceptions
如果您无法使用异常,那么其他设计可以使用另一种方法,其中包含多个案例组合在一起。
- 嵌套列表,用于在 C++ 中实现邻接列表
- 无法获得等效的 std::less 来用于嵌套迭代器
- 用于返回嵌套类类型的作用域解析运算符
- 用于在一维数组上嵌套循环操作的正确 openmp 指令
- 模板函数,用于从多个嵌套unordered_map中提取值
- 用于更简洁代码的嵌套命名空间
- 简单的C 嵌套以用于循环程序,以打印带有星星错误的正方形
- C /STL公共迭代器,用于深嵌套的私人数据
- C 误差C2064用于C 模板中的嵌套结合
- CFG 和 PDA 用于具有完美嵌套括号和括号的语法
- 嵌套 2 用于推入向量的循环,尽量不重复push_back值
- 嵌套c++11范围循环,用于查找组合
- 将OpenMP应用于C++中的特定嵌套循环
- C++中用于嵌套循环的OpenMP编程的锁定策略
- 嵌套,用于在java中不按预期工作
- 嵌套循环,用于显示 25 个字符的行 c++
- openMP 嵌套并行用于循环与内部并行
- 重载运算符<<用于嵌套类模板
- 用于顺序内存访问的编译器嵌套循环优化
- 使嵌套类型哈希能够用于std::unordered_set