(0)有什么更好的方法来避免do-while(0); 黑客在C + +?
当代码stream如下所示:
if(check()) { ... ... if(check()) { ... ... if(check()) { ... ... } } }
我通常看到这个工作,以避免上面的杂乱的代码stream:
do { if(!check()) break; ... ... if(!check()) break; ... ... if(!check()) break; ... ... } while(0);
有什么更好的方法可以避免这种变通/黑客行为,使其成为更高级的(行业级)代码?
任何build议是开箱即用的,欢迎!
在一个函数中隔离这些决定被认为是可以接受的做法,并使用return
而不是break
。 虽然所有这些检查对应于与function相同的抽象级别,但这是相当合理的方法。
例如:
void foo(...) { if (!condition) { return; } ... if (!other condition) { return; } ... if (!another condition) { return; } ... if (!yet another condition) { return; } ... // Some unconditional stuff }
有时候使用goto
实际上是正确的答案 – 至less对那些没有在宗教信仰中长大的人,“不pipe问题是什么, goto
都不可能成为答案” – 这就是其中之一。
do { ... } while(0);
这段代码使用do { ... } while(0);
的hack do { ... } while(0);
唯一的目的是打扮成break
。 如果你打算使用goto
,那就开放吧。 使代码HARDER阅读是没有意义的。
一个特定的情况就是当你有很多代码的情况相当复杂时:
void func() { setup of lots of stuff ... if (condition) { ... ... if (!other condition) { ... if (another condition) { ... if (yet another condition) { ... if (...) ... } } } .... } finish up. }
它实际上可以通过没有这样复杂的逻辑来清除代码是正确的。
void func() { setup of lots of stuff ... if (!condition) { goto finish; } ... ... if (other condition) { goto finish; } ... if (!another condition) { goto finish; } ... if (!yet another condition) { goto finish; } ... .... if (...) ... // No need to use goto here. finish: finish up. }
编辑:澄清,我决不是build议使用goto
作为一个通用的解决scheme。 但有些情况下, goto
是比其他解决scheme更好的解决scheme。
想象一下,例如,我们正在收集一些数据,并且正在testing的不同条件是某种“这是收集的数据的结束” – 这取决于某种“连续/结束”标记,这取决于哪里你在数据stream中。
现在,当我们完成时,我们需要将数据保存到文件中。
是的,通常有其他解决scheme可以提供合理的解决scheme,但并非总是如此。
你可以使用一个简单的连续模式和一个bool
variables:
bool goOn; if ((goOn = check0())) { ... } if (goOn && (goOn = check1())) { ... } if (goOn && (goOn = check2())) { ... } if (goOn && (goOn = check3())) { ... }
这个执行链只要checkN
返回一个false
就会停止。 由于&&
操作员的短路,将不会再执行check...()
调用。 而且,优化编译器足够聪明,可以认识到设置为false
是单向的,为你插入缺失的goto end
。 结果,上面的代码的性能将与do
/ while(0)
的性能完全相同,只是没有可读性的痛苦打击。
-
尝试将代码提取到一个单独的函数(或者可能不止一个)。 如果检查失败,则从函数返回。
-
如果与周围的代码紧密耦合,并且找不到减less耦合的方法,请查看该块之后的代码。 据推测,它清理了该function使用的一些资源。 尝试使用RAII对象来pipe理这些资源; 然后用
return
replace每个可笑的break
(或者throw
,如果这更合适的话),并让对象的析构函数为你清理。 -
如果程序stream程(必然)如此地浪漫以至于你确实需要一个
goto
,那就用它来代替它,而不是用一个怪异的伪装。 -
如果你的代码规则是盲目禁止转换的,而且你不能简化程序stream程,那么你可能不得不用你的黑客来掩饰它。
TLDR : RAII ,事务代码(仅在计算结果或返回时才返回)和exception。
很长的回答:
在C中 ,这种代码的最佳做法是在代码中添加一个EXIT / CLEANUP / other标签,在这里清理本地资源并返回错误代码(如果有的话)。 这是最好的做法,因为它将代码自然地分解为初始化,计算,提交和返回:
error_code_type c_to_refactor(result_type *r) { error_code_type result = error_ok; //error_code_type/error_ok defd. elsewhere some_resource r1, r2; // , ...; if(error_ok != (result = computation1(&r1))) // Allocates local resources goto cleanup; if(error_ok != (result = computation2(&r2))) // Allocates local resources goto cleanup; // ... // Commit code: all operations succeeded *r = computed_value_n; cleanup: free_resource1(r1); free_resource2(r2); return result; }
在C中,在大多数代码库中, if(error_ok != ...
和goto
代码通常隐藏在一些方便的macros( RET(computation_result)
ENSURE_SUCCESS(computation_result, return_code)
RET(computation_result)
, ENSURE_SUCCESS(computation_result, return_code)
等)之后。
C ++通过C提供了额外的工具:
-
清理块function可以实现为RAII,这意味着您不再需要整个
cleanup
块,并使客户端代码能够添加早期返回语句。 -
当你无法继续时,你会抛出
if(error_ok != ...
转换为直接调用。
等效的C ++代码:
result_type cpp_code() { raii_resource1 r1 = computation1(); raii_resource2 r2 = computation2(); // ... return computed_value_n; }
这是最好的做法,因为:
-
这是明确的(即,error handling不明确,algorithm的主要stream程是)
-
编写客户端代码很简单
-
这是最小的
-
很简单
-
它没有重复的代码结构
-
它不使用macros
-
它不使用奇怪的
do { ... } while(0)
构造 -
它是可重用的最小的努力(也就是说,如果我想复制调用
computation2();
到一个不同的函数,我不必确保我添加一个do { ... } while(0)
在新的代码,也没有#define
goto包装macros和清理标签,也没有其他)。
为了完整起见,我添加了一个答案。 其他一些答案指出,大的条件块可以分解成一个单独的function。 但是也有人指出,这种方法多次将条件代码从原始上下文中分离出来。 这是在C ++ 11中将lambda添加到语言中的一个原因。 其他人build议使用lambdas,但没有提供明确的样本。 我已经把这个答案。 令我感到震惊的是,它在许多方面与do { } while(0)
方法非常相似,也许这意味着它仍然是一个伪装的转换。
earlier operations ... [&]()->void { if (!check()) return; ... ... if (!check()) return; ... ... if (!check()) return; ... ... }(); later operations
当然不是答案,而是答案 (为了完整性)
代替 :
do { if(!check()) break; ... ... if(!check()) break; ... ... if(!check()) break; ... ... } while(0);
你可以写:
switch (0) { case 0: if(!check()) break; ... ... if(!check()) break; ... ... if(!check()) break; ... ... }
这仍然是一个变相的转折,但至less它不再是一个循环。 这意味着你不必非常仔细地检查一下是否有一些继续隐藏在块的某处。
该构造也很简单,您可以希望编译器将其优化。
正如@jamesdlin所build议的,你甚至可以隐藏在一个macros后面
#define BLOC switch(0) case 0:
并使用它
BLOC { if(!check()) break; ... ... if(!check()) break; ... ... if(!check()) break; ... ... }
这是可能的,因为C语言语法在开关后期望一个语句,而不是一个括号内的块,你可以在该语句之前放置一个case标签。 到目前为止,我还没有看到允许这一点,但在这种特殊情况下,将交换机隐藏在一个不错的macros观背后是很方便的。
我会推荐一种类似于Mats的方法来减去不必要的goto
。 只有在条件逻辑的function。 任何总是运行的代码应该在调用者调用函数之前或之后执行:
void main() { //do stuff always func(); //do other stuff always } void func() { if (!condition) return; ... if (!other condition) return; ... if (!another condition) return; ... if (!yet another condition) return; ... }
代码stream本身已经是一个在function上发生了很多的代码气味。 如果没有直接的解决scheme(该函数是一个普通的检查函数),那么使用RAII,所以你可以返回而不是跳转到函数的结束部分可能会更好。
对我来说do{...}while(0)
很好。 如果你不想在do{...}while(0)
看到do{...}while(0)
,你可以为它们定义替代关键字。
例:
#define BEGIN_TEST do{ #define END_TEST }while(0); BEGIN_TEST if(!condition1) break; if(!condition2) break; if(!condition3) break; if(!condition4) break; if(!condition5) break; //processing code here END_TEST
我认为编译器会在do{...}while(0)
while(0)
删除while(0)
条件, do{...}while(0)
在二进制版本中则删除do{...}while(0)
,并将中断转换为无条件跳转。 你可以检查它的汇编语言版本是肯定的。
使用goto
也可以生成更简洁的代码,并且可以直接使用条件跳转逻辑。 您可以执行以下操作:
{ if(!condition1) goto end_blahblah; if(!condition2) goto end_blahblah; if(!condition3) goto end_blahblah; if(!condition4) goto end_blahblah; if(!condition5) goto end_blahblah; //processing code here }end_blah_blah:; //use appropriate label here to describe... // ...the whole code inside the block.
注意标签放在closures之后}
。 这是为了避免goto
中的一个可能的问题,就是因为您没有看到标签而意外放置了一个代码。 现在就像do{...}while(0)
没有条件代码。
为了使这个代码更清晰,更容易理解,你可以这样做:
#define BEGIN_TEST { #define END_TEST(_test_label_) }_test_label_:; #define FAILED(_test_label_) goto _test_label_ BEGIN_TEST if(!condition1) FAILED(NormalizeData); if(!condition2) FAILED(NormalizeData); if(!condition3) FAILED(NormalizeData); if(!condition4) FAILED(NormalizeData); if(!condition5) FAILED(NormalizeData); END_TEST(NormalizeData)
有了这个,你可以做嵌套块并指定你想要退出/跳出的位置。
#define BEGIN_TEST { #define END_TEST(_test_label_) }_test_label_:; #define FAILED(_test_label_) goto _test_label_ BEGIN_TEST if(!condition1) FAILED(NormalizeData); if(!condition2) FAILED(NormalizeData); BEGIN_TEST if(!conditionAA) FAILED(DecryptBlah); if(!conditionBB) FAILED(NormalizeData); //Jump out to the outmost block if(!conditionCC) FAILED(DecryptBlah); // --We can now decrypt and do other stuffs. END_TEST(DecryptBlah) if(!condition3) FAILED(NormalizeData); if(!condition4) FAILED(NormalizeData); // --other code here BEGIN_TEST if(!conditionA) FAILED(TrimSpaces); if(!conditionB) FAILED(TrimSpaces); if(!conditionC) FAILED(NormalizeData); //Jump out to the outmost block if(!conditionD) FAILED(TrimSpaces); // --We can now trim completely or do other stuffs. END_TEST(TrimSpaces) // --Other code here... if(!condition5) FAILED(NormalizeData); //Ok, we got here. We can now process what we need to process. END_TEST(NormalizeData)
意大利面代码并不是goto
的错,这是程序员的错。 你仍然可以生成意大利面代码,而不使用goto
。
如果你不需要在执行过程中引入局部variables,那么你通常可以把这个变成扁平的:
if (check()) { doStuff(); } if (stillOk()) { doMoreStuff(); } if (amIStillReallyOk()) { doEvenMore(); } // edit doThingsAtEndAndReportErrorStatus()
与dasblinkenlight的答案类似,但是避免了在代码审阅者可以“修复”的情况下的赋值:
bool goOn = check0(); if (goOn) { ... goOn = check1(); } if (goOn) { ... goOn = check2(); } if (goOn) { ... }
…
当在下一步之前需要检查一个步骤的结果时,我使用这个模式,这与所有的检查都可以用大if( check1() && check2()...
type模式。
使用例外。 您的代码看起来会更清晰(而且正是为了处理程序执行stream程中的错误而创build了exception)。 为了清理资源(文件描述符,数据库连接等),请阅读文章为什么C ++不提供“finally”构造? 。
#include <iostream> #include <stdexcept> // For exception, runtime_error, out_of_range int main () { try { if (!condition) throw std::runtime_error("nope."); ... if (!other condition) throw std::runtime_error("nope again."); ... if (!another condition) throw std::runtime_error("told you."); ... if (!yet another condition) throw std::runtime_error("OK, just forget it..."); } catch (std::runtime_error &e) { std::cout << e.what() << std::endl; } catch (...) { std::cout << "Caught an unknown exception\n"; } return 0; }
从function编程的angular度来看,这是一个众所周知的问题,也许是monad。
为了回应我在下面收到的评论,我在这里编辑了我的介绍:你可以在各个地方find关于实现C ++ monad的全部细节,这将使你能够达到Rotors的build议。 需要一段时间才能使单子成为可能,所以我要在这里build议一个快速的“穷人”单子机制,你只需要知道boost :: optional就可以了。
设置你的计算步骤如下:
boost::optional<EnabledContext> enabled(boost::optional<Context> context); boost::optional<EnergisedContext> energised(boost::optional<EnabledContext> context);
每个计算步骤显然可以做一些类似于返回boost::none
如果可选的它是空的。 举个例子:
struct Context { std::string coordinates_filename; /* ... */ }; struct EnabledContext { int x; int y; int z; /* ... */ }; boost::optional<EnabledContext> enabled(boost::optional<Context> c) { if (!c) return boost::none; // this line becomes implicit if going the whole hog with monads if (!exists((*c).coordinates_filename)) return boost::none; // return none when any error is encountered. EnabledContext ec; std::ifstream file_in((*c).coordinates_filename.c_str()); file_in >> ec.x >> ec.y >> ec.z; return boost::optional<EnabledContext>(ec); // All ok. Return non-empty value. }
然后链接在一起:
Context context("planet_surface.txt", ...); // Close over all needed bits and pieces boost::optional<EnergisedContext> result(energised(enabled(context))); if (result) { // A single level "if" statement // do work on *result } else { // error }
关于这一点的好处是,您可以为每个计算步骤编写明确定义的unit testing。 此外,调用读取像简单的英语(通常是function风格的情况下)。
如果你不关心不可变性,每次使用shared_ptr或类似的方法得到一些变化,返回相同的对象会更方便。
如何将if语句转换为一个额外的函数,产生一个数值或枚举结果?
int ConditionCode (void) { if (condition1) return 1; if (condition2) return 2; ... return 0; } void MyFunc (void) { switch (ConditionCode ()) { case 1: ... break; case 2: ... break; ... default: ... break; } }
也许这样的事情
#define EVER ;; for(EVER) { if(!check()) break; }
或使用例外
try { for(;;) if(!check()) throw 1; } catch() { }
使用exception你也可以传递数据。
在这种情况下,我并不特别使用break
或return
的方式。 鉴于我们通常在面对这样的情况时,通常是一个相对较长的方法。
如果我们有多个退出点,当我们想要知道什么会导致某个逻辑被执行时,可能会造成困难:通常我们只是继续向上去封闭这个逻辑块,封闭块的标准告诉我们情况:
例如,
if (conditionA) { .... if (conditionB) { .... if (conditionC) { myLogic(); } } }
通过查看封闭块,很容易发现myLogic()
仅在conditionA and conditionB and conditionC
为true时才会发生。
有早期回报时,它变得不那么明显:
if (conditionA) { .... if (!conditionB) { return; } if (!conditionD) { return; } if (conditionC) { myLogic(); } }
我们不能再从myLogic()
导航,查看封闭的块来找出状况。
我使用了不同的解决方法。 这是其中之一:
if (conditionA) { isA = true; .... } if (isA && conditionB) { isB = true; ... } if (isB && conditionC) { isC = true; myLogic(); }
(当然,欢迎使用相同的variables来replace所有的isA isB isC
。)
这样的方法至less会给代码的读者,当isB && conditionC
时isB && conditionC
myLogic()
被执行。 给读者一个提示,他需要进一步查找什么会导致isB为真。
typedef bool (*Checker)(); Checker * checkers[]={ &checker0,&checker1,.....,&checkerN,NULL }; bool checker1(){ if(condition){ ..... ..... return true; } return false; } bool checker2(){ if(condition){ ..... ..... return true; } return false; } ...... void doCheck(){ Checker ** checker = checkers; while( *checker && (*checker)()) checker++; }
那个怎么样?
Another pattern useful if you need different cleanup steps depending on where the failure is:
private ResultCode DoEverything() { ResultCode processResult = ResultCode.FAILURE; if (DoStep1() != ResultCode.SUCCESSFUL) { Step1FailureCleanup(); } else if (DoStep2() != ResultCode.SUCCESSFUL) { Step2FailureCleanup(); processResult = ResultCode.SPECIFIC_FAILURE; } else if (DoStep3() != ResultCode.SUCCESSFUL) { Step3FailureCleanup(); } ... else { processResult = ResultCode.SUCCESSFUL; } return processResult; }
I'm not a C++ programmer, so I won't write any code here, but so far nobody has mentioned an object oriented solution. So here is my guess on that:
Have a generic interface that provides a method to evaluate a single condition. Now you can use a list of implementations of those conditions in your object containing the method in question. You iterate over the list and evaluate each condition, possibly breaking out early if one fails.
The good thing is that such design sticks very well to the open/closed principle , because you can easily add new conditions during initialization of the object containing the method in question. You can even add a second method to the interface with the method for condition evaluation returning a description of the condition. This can be used for self-documenting systems.
The downside, however, is that there is slightly more overhead involved because of the usage of more objects and the iteration over the list.
This is the way I do it.
void func() { if (!check()) return; ... ... if (!check()) return; ... ... if (!check()) return; ... ... }
First, a short example to show why goto
is not a good solution for C++:
struct Bar { Bar(); }; extern bool check(); void foo() { if (!check()) goto out; Bar x; out: }
Try to compile this into an object file and see what happens. Then try the equivalent do
+ break
+ while(0)
.
That was an aside. The main point follows.
Those little chunks of code often require some kind of cleanup should the whole function fail. Those cleanups usually want to happen in the opposite order from the chunks themselves, as you "unwind" the partially-finished computation.
One option to obtain these semantics is RAII ; see @utnapistim's answer. C++ guarantees that automatic destructors run in the opposite order to constructors, which naturally supplies an "unwinding".
But that requires lots of RAII classes. Sometimes a simpler option is just to use the stack:
bool calc1() { if (!check()) return false; // ... Do stuff1 here ... if (!calc2()) { // ... Undo stuff1 here ... return false; } return true; } bool calc2() { if (!check()) return false; // ... Do stuff2 here ... if (!calc3()) { // ... Undo stuff2 here ... return false; } return true; }
…and so on. This is easy to audit, since it puts the "undo" code next to the "do" code. Easy auditing is good. It also makes the control flow very clear. It is a useful pattern for C, too.
It can require the calc
functions to take lots of arguments, but that is usually not a problem if your classes/structs have good cohesion. (That is, stuff that belongs together lives in a single object, so these functions can take pointers or references to a small number of objects and still do lots of useful work.)
If you code has a long block of if..else if..else statements, you may try and rewrite the entire block with the help of Functors
or function pointers
. It may not be the right solution always but quite often is.
http://www.cprogramming.com/tutorial/functors-function-objects-in-c++.html
I am amazed by the number of different answers being presented here. But, finally in the code which I have to change (ie remove this do-while(0)
hack or anything), I did something different from any of the answers being mentioned here and I am confused why no one thought this. 以下是我所做的:
Initial code:
do { if(!check()) break; ... ... if(!check()) break; ... ... if(!check()) break; ... ... } while(0); finishingUpStuff.
现在:
finish(params) { ... ... } if(!check()){ finish(params); return; } ... ... if(!check()){ finish(params); return; } ... ... if(!check()){ finish(params); return; } ... ...
So, what has been done here is that the finishing up stuff has been isolated in a function and things have suddenly become so simple and clean!
I thought this solution was worth mentioning, so provided it here.
Consolidate it into one if
statement:
if( condition && other_condition && another_condition && yet_another_condition && ... ) { if (final_cond){ //Do stuff } else { //Do other stuff } }
This is the pattern used in languages such as Java where the goto keyword was removed.
If using the same error handler for all errors, and each step returns a bool indicating success:
if( DoSomething() && DoSomethingElse() && DoAThirdThing() ) { // do good condition action } else { // handle error }
(Similar to tyzoid's answer, but conditions are the actions, and the && prevents additional actions from occurring after the first failure.)
Why wasn't flagging method answered it is used since ages.
//you can use something like this (pseudocode) long var = 0; if(condition) flag a bit in var if(condition) flag another bit in var if(condition) flag another bit in var ............ if(var == certain number) { Do the required task }