2012-10-09 64 views
6

我有一個關於在C++中使用goto語句的問題。我明白這個話題是有爭議的,並且對任何廣泛的建議或論點都不感興趣(我通常偏離使用goto)。相反,我有一個特定的情況,並希望瞭解我的解決方案(使用goto語句)是否合適。我不會稱自己爲C++新手,但不會將自己歸類爲專業級程序員。生成我的問題的部分代碼一旦啓動,將以無限循環形式旋轉。在僞線的一般流程如下:使用goto乾淨地退出循環

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成爲假;最後是如果在循環底部滿足結束條件。我也許還應該注意,main_loop可以在ControlLoop對象的整個生命週期中啓動並完成多次,所以它應該在之後以m_bIsRunning = false乾淨地退出。

另外,我意識到我可以在這裏使用break關鍵字,但main_loop中的大部分僞代碼函數調用並沒有真正封裝爲函數,僅僅是因爲它們需要有很多參數,或者它們都需要訪問成員變量。在我看來,這兩種情況會比將main_loop作爲一個更長的函數更容易混淆,並且由於大while循環的長度,像goto loop_end這樣的語句似乎更清晰地顯示給我。

現在的問題:如果您要將它寫入自己的代碼,這種解決方案會讓您感覺不舒服嗎?它對我來說確實感覺有點不對,但之後我從未在C++代碼中使用goto語句 - 因此我請求專家幫忙。我還有沒有其他的基本想法可以讓代碼更清晰?

謝謝。

+4

「使用RAII對我來說沒有多大意義」使用RAII總是很有意義。總是。所有的時間。 –

+10

爲什麼不在這裏使用'break'? –

+3

如果只有一個你打破的循環,使用'break';它更清潔和更清晰。如果你有多個循環級別,或者如果你在'switch'中,並且你需要退出所有的循環,那麼'goto'就可以。 –

回答

3

有使環打破早期我會簡單地使用你的一個,奇異條件break。不需要goto這就是break的用途。

然而,這些函數調用,如果任何可以拋出一個異常,或者如果你最終需要多個break的I寧願一個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當函數返回。

+0

這個解決方案非常乾淨。謝謝。 – Hunter

+0

考慮到有'InitializeXXX'函數,我建議將初始化放在構造函數中,以便對稱。 –

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; 
} 

字面上說同樣的事情內置語法更具可讀性。

+0

我的主要擔憂是真正的代碼中的while循環很長,所以我的想法是使用break使得它不太清楚程序流將立即讀取它的位置。但是,謝謝你的評論。這似乎是一個合理的觀點。 – Hunter

+0

只要您的代碼中沒有單獨的空格,那麼您的代碼應該使用'while {}'花括號向任何有經驗的開發人員流暢地讀取。遵循循環之外的更少是特別容易的。 – pickypg

+0

此外,IDE可以準確告訴你'break'會去哪裏(儘管我不知道是否有IDE具有該特定功能)。 –

1

而不是使用goto,您應該使用break;轉義循環。

+1

對於單一級別的循環,這是真的。怎麼樣多層次的循環? –

+1

海報正在使用goto退出該單一循環,因此,在OP的情況下,break是最好的(IMO)選項。 – zachjs

+1

同意;你已經回答了問題的來信。但是超出問題信件和回答問題精神的答案更有可能被提高。問題不是'goto'自動壞;在這個例子中這是不正確的,但是在合適的情況下設計環境並不困難。 –

3

UPDATE

如果您主要關注的是while循環太長,那麼你的目標應該是把它縮短了,C++是一種面向對象的語言,面向對象是分裂的事情小塊組成部分,甚至是在一般的非OO語言中,我們通常仍然認爲我們應該將一個方法/循環分解爲一個小的方法,並使其簡單易讀。如果一個循環中有300行,不管break/goto是不是真的節省了你的時間,不是嗎?

UPDATE

我不反對轉到,但我不會在這裏爲你做什麼,我寧願只使用突破,一般到,他看到一個休息開發者使用它在那裏,他知道這意味着轉到了這一段時間,並且通過這種方式,他可以很容易地意識到它實際上在幾秒鐘內退出循環。是的轉到可能會節省幾秒鐘的時間來理解它,但它也可能讓人們對你的代碼感到緊張。

的事情,我可以想像,我使用goto語句將退出二級循環:

while(running) 
{ 
    ... 
    while(runnning2) 
    { 
     if(bad_data) 
     { 
      goto loop_end; 
     } 
    } 
    ... 
} 
loop_end: 
+0

謝謝,西蒙。主循環不是很長(〜150行),但比一個屏幕長,至少對我而言。我當然可以分解它。不過,我已經達到了我認爲合理的程度。這可能屬於另一個問題,如果這樣可以自由地不回答,但是你會做出這樣的子程序類方法,它們通過在類中存儲數據進行通信,或者只是在具有輸出參數的相同翻譯單元中進行函數操作。 – Hunter

+0

嗯,這真的取決於我,我不知道我會做什麼,直到我做了;)我主要遵循的規則將使每個班只專注於一個領域,只是做一件事,但沒有人是完美的,我可能會做出一些我無法察覺的醜陋編碼。如果你和你的團隊都感覺很好,並且沒有人願意刪除它,我會在這裏說出代碼。 –

0

break是一個選項時,轉到退出循環不是好習慣。

另外,在複雜的例程中,最好只有一個退出邏輯(清理)放在最後。 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; 
} 
1

有幾種替代goto:根據情況breakcontinuereturn

但是,您需要記住breakcontinue都是有限的,因爲它們隻影響最內部的循環。另一方面,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(); 
} 

注:如果您的身體功能不能舒適適合您的屏幕,你就錯了。

+0

謝謝,這是另一個值得考慮的解決方案。 – Hunter

0

我看到很多人建議break而不是goto。但break不比goto「更好」(或「更差」)。

goto的探究有效地開始與Dijkstra的"Go To Considered Harmful" paper早在1968年,當意大利麪條代碼是規則之類的東西塊結構ifwhile報表仍被視爲前沿。 ALGOL 60擁有它們,但它基本上是學者們使用的一種研究語言(參見今天的ML);作爲當時主流語言之一的Fortran,不會再讓他們再待9年!

在Dijkstra的論文的主要觀點是:

  1. 人類善於空間推理,和塊的結構化程序上把握,因爲在時間上彼此靠近發生程序的行爲在彼此接近的描述「空間「(程序代碼);
  2. 如果您避開goto的各種形式,那麼有可能知道事情關於程序中每個詞彙位置上變量的可能狀態。特別是,在while循環的末尾,您知道該循環的條件必須是錯誤的。這對調試很有用。 (Dijkstra算法並不完全這樣說,但你可以推斷出它。)

break,就像goto(和return S早期和異常...),降低了(1)和消除(2)。當然,使用break通常可以避免爲while條件寫出複雜的邏輯,從而讓您獲得可理解性的淨收益 - 而goto也完全相同。

+0

感謝您的解釋。在我的具體情況下,如果沒有'break'或'goto'也是很好的,while循環的其餘部分需要用一個額外的if來保護,因爲根據錯誤的輸入向硬件發送輸出可能是災難性的即對不良數據的正確響應是「立即停止」。 – Hunter