6

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の終了時に自動的にクリーンアップできるハードウェアの一種の「セッション」または「使用」を表す一時オブジェクトを作成させることができると思いますが、そのアイデアが誰かにそれをより明確にするかどうかはわかりませんそうでなければ私の意図は何ですか。ループから抜け出す方法は3つしかありません。1つは、外部ハードウェアから不正なデータが読み取られた場合です。2つ目は、ProcessPendingEvents()がユーザーによる中止を示している場合です。これにより、m_bIsRunningがfalseになります。最後は、ループの最下部で終了条件が満たされた場合です。m_bIsRunning = falseその後。

また、ここでbreakキーワードを使用できることも理解していますが、main_loop内のこれらの擬似コード関数呼び出しのほとんどは、多くの引数が必要であるか、すべてがメンバー変数にアクセスする必要があるため、実際には関数としてカプセル化されていません。私の意見では、これらのケースはどちらも、単にmain_loopを長い関数として残すよりも混乱を招きます。また、大きなwhileループの長さのために、のようなステートメントはgoto loop_end私にはより明確に読めるようです。

さて、質問です。このソリューションを独自のコードで記述した場合、このソリューションは不快になりますか?私には少し違和感がありますが、C ++コードでこれまでgotoステートメントを使用したことはありません。したがって、専門家に助けを求めています。このコードをより明確にするために私が見逃している他の基本的なアイデアはありますか?

ありがとう。

4

7 に答える 7

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

これは文字通り、組み込み構文で同じことを言います。

于 2012-10-09T02:44:34.273 に答える
3

アップデート

while ループが長すぎることが主な懸念事項である場合は、それを短くすることを目指す必要があります。C++ は OO 言語であり、OO は物事を小さな断片とコンポーネントに分割するためのものです。メソッド/ループを小さなものに分割し、読みやすく短くする必要があります。ループに 300 行ある場合、break/goto を使用しても実際には時間が節約されませんよね?

アップデート

私はgotoに反対しているわけではありませんが、ここではあなたのように使用しません。一般的にはbreakを使用することを好みます。開発者はそこでブレークを見たので、その間の最後に goto を意味することを知っています。そのm_bIsRunningで= falseの場合、数秒以内に実際にループを終了していることを容易に認識できます。はい、gotoはそれを理解する時間を数秒節約するかもしれませんが、人々があなたのコードに神経質になるかもしれません。

goto を使用していると想像できることは、2 レベルのループを終了することです。

while(running) 
{
    ...
    while(runnning2)
    {
        if(bad_data)
        {
            goto loop_end;
        }
    }
    ...
}
loop_end:
于 2012-10-09T02:51:27.120 に答える
3

ループを早期に中断させる特異な条件では、単純に . を使用しbreakます。gotoそのための必要はありませんbreak

ただし、これらの関数呼び出しのいずれかbreakが例外をスローする可能性がある場合、または複数の s が必要になる場合、私は 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その関数が戻ったときに常に呼び出します。

于 2012-10-09T02:58:15.183 に答える
1

を使用する代わりに、 を使用してループをエスケープするgoto必要があります。break;

于 2012-10-09T02:44:53.577 に答える
1

goto: breakcontinueおよびreturn状況に応じて、いくつかの代替手段があります。

breakただし、とcontinueはどちらも、最も内側のループにのみ影響するという制限があることに注意する必要があります。return一方、この制限の影響を受けません。

一般に、 a を使用して特定のスコープ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();
}

注: 関数本体が画面にうまく収まらない場合は、間違っています。

于 2012-10-09T06:44:16.050 に答える
0

Goto はbreak、オプションの場合にループを終了するための良い方法ではありません。

また、複雑なルーチンでは、終了ロジックを 1 つだけ (クリーンアップ付きで) 最後に配置することをお勧めします。Goto は、return ロジックにジャンプするために使用されることがあります。

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;
}
于 2012-10-09T03:01:40.420 に答える