我有一个关于在C ++中使用goto语句的问题。我理解这个话题是有争议的,并且对任何广泛的建议或论点都不感兴趣(我通常偏离使用goto
)。相反,我有一个特定的情况,并想要了解我的解决方案,它使用goto语句,是否是一个好的。我不会称自己是C ++的新手,但也不会将自己归类为专业级程序员。生成我的问题的代码部分一旦启动就会在无限循环中旋转。伪代码中线程的一般流程如下:
void ControlLoop::main_loop()
{
InitializeAndCheckHardware(pHardware) //pHardware is a pointer given from outside
//The main loop
while (m_bIsRunning)
{
simulated_time += time_increment; //this will probably be += 0.001 seconds
ReadSensorData();
if (data_is_bad) {
m_bIsRunning = false;
goto loop_end;
}
ApplyFilterToData();
ComputeControllerOutput();
SendOutputToHardware();
ProcessPendingEvents();
while ( GetWallClockTime() < simulated_time ) {}
if ( end_condition_is_satisified ) m_bIsRunning = false;
}
loop_end:
DeInitializeHardware(pHardware);
}
pHardware指针从ControlLoop对象外部传入并具有多态类型,因此对我来说使用RAII并在main_loop中创建和破坏硬件接口本身没有多大意义。我想我可以让pHardware创建一个临时对象,代表硬件的“会话”或“使用”,可以在main_loop退出时自动清理,但我不确定这个想法是否会让某些人更清楚否则我的意图是什么。循环中只有三种方式:第一种方法是从外部硬件读取坏数据;第二个是如果ProcessPendingEvents()指示用户启动的中止,这只会导致m_bIsRunning变为false;最后一个是如果在循环的底部满足结束条件。我还应该注意,main_loop可以在ControlLoop对象的生命周期内多次启动和完成,因此它应该在m_bIsRunning = false
之后干净地退出。
另外,我意识到我可以在这里使用break关键字,但是main_loop中的大多数伪代码函数调用并没有真正封装为函数,只是因为它们需要有很多参数,或者它们都需要访问成员变量。在我看来,这两种情况都比让简单地将main_loop作为一个更长的函数更容易混淆,而且由于big while循环的长度,像goto loop_end
这样的语句似乎对我来说更清晰。
现在提出一个问题:如果您在自己的代码中编写它,这个解决方案会让您感到不舒服吗?它对我来说确实有些不对劲,但之后我从未在C ++代码中使用过goto语句 - 因此我请求专家提供帮助。我还缺少哪些其他基本想法可以使这些代码更清晰?
感谢。
答案 0 :(得分:5)
在面向对象的开发中,避免使用goto
是非常可靠的事情。
在您的情况下,为什么不使用break
退出循环?
while (true)
{
if (condition_is_met)
{
// cleanup
break;
}
}
关于你的问题:你使用goto
会让我感到不舒服。 break
可读性较差的唯一原因是您不能成为强大的C ++开发人员。对于任何经验丰富的类似C语言的开发人员,break
都会更好地阅读,并提供比goto
更清晰的解决方案。
特别是,我根本不同意
if (something)
{
goto loop_end;
}
比
更具可读性if (something)
{
break;
}
字面意思是内置语法相同的东西。
答案 1 :(得分:3)
<强>更新强>
如果你主要担心的是while循环太长,那么你的目标应该是缩短它,C ++是一种OO语言,而OO则用于分割小块和组件,即使在一般的非OO语言中我们一般仍然认为我们应该将一个方法/循环分解为一个小方法,并使其简单易读。如果一个循环中有300行,那么无论是break / goto还是没有真正节省你的时间就没有了吗?
<强>更新强>
我并不反对 goto ,但我不会像你一样使用它,我更喜欢使用 break ,一般来说,他看到了一个< strong> break 在那里,他知道这意味着转到最后,并且凭借 m_bIsRunning = false ,他可以很容易地意识到它实际上是在几秒钟内退出循环。是的,转到可以节省几秒钟的时间来理解它,但也可能让人们对您的代码感到紧张。
我可以想象我正在使用goto将退出两级循环:
while(running)
{
...
while(runnning2)
{
if(bad_data)
{
goto loop_end;
}
}
...
}
loop_end:
答案 2 :(得分:3)
使用你的那个,奇怪的条件导致循环提前破坏我只会使用break
。 goto
不需要break
。
但是,如果那些函数调用中的任何都会抛出异常,或者如果你最终需要多个break
,我宁愿使用RAII样式的容器,这就是确切的事情。析构函数是为了。您始终执行对DeInitializeHardware
的调用,因此......
// todo: add error checking if needed
class HardwareWrapper {
public:
HardwareWrapper(Hardware *pH)
: _pHardware(pH) {
InitializeAndCheckHardware(_pHardware);
}
~HardwareWrapper() {
DeInitializeHardware(_pHardware);
}
const Hardware *getHardware() const {
return _pHardware;
}
const Hardware *operator->() const {
return _pHardware;
}
const Hardware& operator*() const {
return *_pHardware;
}
private:
Hardware *_pHardware;
// if you don't want to allow copies...
HardwareWrapper(const HardwareWrapper &other);
HardwareWrapper& operator=(const HardwareWrapper &other);
}
// ...
void ControlLoop::main_loop()
{
HardwareWrapper hw(pHardware);
// code
}
现在,无论发生什么情况,当该函数返回时,您将始终调用DeInitializeHardware
。
答案 3 :(得分:1)
不应使用goto
,而应使用break;
来转义循环。
答案 4 :(得分:1)
goto
:break
,continue
和return
有多种替代方案,视情况而定。
但是,您需要记住,break
和continue
都受到限制,因为它们只会影响最内层循环。另一方面,return
不受此限制的影响。
通常,如果您使用goto
退出特定范围,则可以使用其他函数和return
语句进行重构。它可能会使代码更容易阅读作为奖励:
// Original
void foo() {
DoSetup();
while (...) {
for (;;) {
if () {
goto X;
}
}
}
label X: DoTearDown();
}
// Refactored
void foo_in() {
while (...) {
for (;;) {
if () {
return;
}
}
}
}
void foo() {
DoSetup();
foo_in();
DoTearDown();
}
注意:如果你的功能体不能舒服地放在你的屏幕上,你就错了。
答案 5 :(得分:0)
当break
是一个选项时,Goto不是退出循环的好习惯。
此外,在复杂的例程中,最好只放置一个退出逻辑(带清理)。 Goto有时用于跳转到返回逻辑。
来自QEMU vmdk块驱动程序的示例:
static int vmdk_open(BlockDriverState *bs, int flags)
{
int ret;
BDRVVmdkState *s = bs->opaque;
if (vmdk_open_sparse(bs, bs->file, flags) == 0) {
s->desc_offset = 0x200;
} else {
ret = vmdk_open_desc_file(bs, flags, 0);
if (ret) {
goto fail;
}
}
/* try to open parent images, if exist */
ret = vmdk_parent_open(bs);
if (ret) {
goto fail;
}
s->parent_cid = vmdk_read_cid(bs, 1);
qemu_co_mutex_init(&s->lock);
/* Disable migration when VMDK images are used */
error_set(&s->migration_blocker,
QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
"vmdk", bs->device_name, "live migration");
migrate_add_blocker(s->migration_blocker);
return 0;
fail:
vmdk_free_extents(bs);
return ret;
}
答案 6 :(得分:0)
我看到很多人建议break
而不是goto
。但break
与goto
相比并非“更好”(或“更差”)。
对goto
的调查有效地开始于1968年Dijkstra的"Go To Considered Harmful" paper,当时意大利面条代码是规则,而块结构if
和while
陈述之类的东西仍然存在被认为是尖端的。 ALGOL 60拥有它们,但它本质上是学术界使用的一种研究语言(参见今天的ML);当时占主导地位的语言之一的Fortran不会再获得9年了!
Dijkstra论文的主要观点是:
goto
,那么就可以知道关于程序中每个词汇位置的变量的可能状态。特别是,在while
循环结束时,您知道该循环的条件必须为false。这对调试很有用。 (Dijkstra并没有这么说,但你可以推断它。) break
,就像goto
(和早期return
一样,以及例外...),减少(1)并消除(2)。当然,使用break
通常可以避免为while
条件编写复杂的逻辑,从而在可理解性方面获得净收益 - 而goto
则完全相同。