作者:梨梨喵
链接:https://www.zhihu.com/question/27922046/answer/73347171
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
关于goto的使用, 一个是跳出多层循环时使用, 还有一个是在性能考虑. 其他场景下基本上没有goto什么事, 特别在现代C++的情况下.我的个人结论是除非是非goto不可的情况下绝不使用goto. 喜欢用goto说明姿势水平还不够高, 没有找到能很好描述逻辑的方式和工具, 还需要学习一个.之前在读源码的时候也发现了一些使用goto情况, 如@蓝色说的统一的错误处理情况和统一的资源管理的情况. 不过在有了C++11 lambda后goto存在的意义就不大了, 统一的错误处理和资源管理可以交给RAII+lambda.如有以下一情况:
HANDLE func()
{
HANDLE h = CreateSomeObject(...);
//...
if(error0_occur)
{
goto err_exit;
}
//...
if(error1_occur)
{
goto err_exit;
}
//...
return h;
err_exit:
CloseHandle(h);
return nullptr;
}
在有了C++11 lambda后, 可以交给RAII来管理了, goto在这种情景下使用的意义并不大了
class ScopeGuard
{
public:
explicit ScopeGuard(std::function<void()> onExit)
: __onExit(onExit), __dismissed(false)
{ }
~ScopeGuard()
{
if (!__dismissed)
{
__onExit();
}
}
void Dismiss(bool dismissed=true)
{
__dismissed = dismissed;
}
ScopeGuard(ScopeGuard const&) = delete;
ScopeGuard& operator=(ScopeGuard const&) = delete;
// non-copyable
private:
std::function<void()> __onExit;
bool __dismissed;
};
HANDLE func()
{
HANDLE h = CreateSomeObject(...);
ScopeGuard onErrorExit([&] { CloseHandle(h); h=nullptr; });
//...
if(error0_occur)
{
return nullptr;
}
//...
if(error1_occur)
{
return nullptr;
}
//...
onErrorExit.Dismiss() // no error occur
return h;
}
另一点, 在关于性能方面goto在VM上有一定作用如一个简单的指令循环如下:
while (byte_code.size()>IP)
{
switch (byte_code[IP])
{
case BYTE_CODE::INSTRUCTION_0:
{
//some action for instruction 0
IP += sizeof(instruction_0);
break;
}
case BYTE_CODE::INSTRUCTION_1:
{
//some action for instruction 1
IP += sizeof(instruction_1);
break;
}
case BYTE_CODE::INSTRUCTION_2:
{
//some action for instruction 2
IP += sizeof(instruction_2);
break;
}
//...
//more cases
}
}
这么写的话在switch和while上出现了至少两个分支语句, 一次指令循环中要进行一次条件跳转和三次无条件跳转(开启switch跳转表优化后)在使用goto语句后可以大大减少在这些控制语句上的性能消耗, 配合GCC的拓展Labels as Values使用.
static const void *next_instr[] = {
&&byte_code_halt,
&&byte_code_instr0,
&&byte_code_instr1,
&&byte_code_instr2,
//...
}
switch (byte_code[IP]) //for initial instruction
{
case BYTE_CODE::HALT: //end of loop
{
byte_code_halt:
break;
}
case BYTE_CODE::INSTRUCTION_0:
{
byte_code_instr0:
//some action for instruction 0
IP += sizeof(instruction_0);
goto *(next_instr[byte_code[IP]]);
}
case BYTE_CODE::INSTRUCTION_1:
{
byte_code_instr1:
//some action for instruction 1
IP += sizeof(instruction_1);
goto *(next_instr[byte_code[IP]]);
}
case BYTE_CODE::INSTRUCTION_2:
{
byte_code_instr2:
//some action for instruction 2
IP += sizeof(instruction_2);
goto *(next_instr[byte_code[IP]]);
}
//...
//more cases
}
在指令流结束的位置插入一条halt指令表示终止. 这样就一次指令循环中只有一次无条件跳转. 性能上能获得一定的提高.之前在我自己写VM的时候做profile, 发现在switch和while上至少消耗了12%的性能, 改用goto后获得了7%的性能的提升.在这一点上goto还是有一定的意义的.