Should I avoid “goto” in situations like this?

我的未来我决定 提交于 2020-01-24 09:22:28

问题


I was making a "concatenating iterator", i.e. an iterator that would iterate over the ints in an int**.

Its constructor needs:

  • An array of T**, representing the beginning of each sub-array.
  • An array of T**, representing the end of each sub-array.

Lo and behold, I ran across a situation where goto seemed to be appropriate.

But something within me screamed "NO!!" so I thought I'd come here and ask:

Should I try avoid goto situations like this? (Does it improve the readability if I do?)

#include <algorithm>

template<class T>
class lazy_concat_iterator
{
    // This code was meant to work for any valid input iterator
    // but for easier reading, I'll assume the type is: T**

    mutable T** m_endIt;              // points to an array of end-pointers
    mutable T** m_it;                 // points to an array of begin-pointers
    mutable bool m_started;   // have we started iterating?
    mutable T* m_sub;         // points somewhere in the current sub-array
    mutable T* m_subEnd;      // points to the end of the current sub-array

public:
    lazy_concat_iterator(T** begins, T** ends)
        : m_it(begins), m_endIt(ends), m_started(false) { }

    void ensure_started() const
    {
        if (!m_started)
        {
            m_started = true;

        INIT:
            m_sub = *m_it;
            m_subEnd = *m_endIt;

            if (m_sub == m_subEnd)  // End of this subarray?
            {
                ++m_it;
                ++m_endIt;
                goto INIT;  // try next one         <<< should I use goto here?
            }
        }
    }
};

How you could use it:

#include <vector>
#include <cstring>

using namespace std;

int main(int argc, char* argv[])
{
    vector<char*> beginnings(argv, argv + argc);

    vector<char*> endings;
    for (int i = 0; i < argc; i++)
        endings.push_back(argv[i] + strlen(argv[i]));

    lazy_concat_iterator<char> it(&beginnings[0], &endings[0]);
    it.ensure_started();  // 'it' would call this internally, when dereferenced
}

回答1:


Yes, you can and should avoid goto, for example this code should do the equivalent for what yours does from the INIT label (this also works for input iterators which was a "hidden requirement" as it doesn't dereference m_it and m_endIt an extra time once the condition is met unlike my previous transformation):

while ((m_subIt = *m_it) == (m_subEnd = *m_endIt))
{
    ++m_it;
    ++m_endIt;
}

Previous answer attempt:

Even a forever loop would be clearer and neater than a goto. It highlights the obvious "never terminate" possibility even better.

    for (;;)
    {
        m_sub = *m_it;
        m_subEnd = *m_endIt;

        if (m_sub != m_subEnd)
            break;

        ++m_it;
        ++m_endIt;
    }

Although I don't see why you need to assign to m_subEnd and m_subIt inside the loop. If you don't you can rewrite this as a while loop:

while (*m_it == *m_endIt)
{
    ++m_it;
    ++m_endIt;
}

m_subIt = *m_it;
m_subEnd = *m_endIt;



回答2:


while (*m_it == *m_endIt)
{
    ++m_it;
    ++m_endIt;
}

m_sub = *m_it;
m_subEnd = *m_endIt;



回答3:


Maybe no for loop, but maybe a do-while?

    do {
        m_sub = *m_it;
        m_subEnd = *m_endIt;

        if (m_sub == m_subEnd)  // End of this subarray?
        {
            ++m_it;
            ++m_endIt;
        }
    } while (m_sub == m_subEnd);

If you don't want to do the comparison twice and still avoid using one of goto's stealth cousins break or continue:

    bool anotherround = FALSE;
    do {
        m_sub = *m_it;
        m_subEnd = *m_endIt;

        anotherround = m_sub == m_subEnd
        if (anotherround)  // End of this subarray?
        {
            ++m_it;
            ++m_endIt;
        }
    } while (anotherround);

With your knowledge of the context I'm sure you can invent better varnames, but that's the idea.

Regarding a goto's influence on readability: for me the main issue with a goto herey is that it forces the programmer to memorize a potential nonlogical movement in the code - all of a sudden the code can jump almost anywhere. If you use control structures, even if you have to introduce some extra lines or whatnot, the program continues to behave as expected and follow the flow. And in the long run, that's what readability is all about.




回答4:


Don't use a goto. The only case when a goto can be forgiven is if you have a complicated function (which you shouldn't have anyways) and you want to have a centralized exit/cleanup part at the end of the function, where you could goto upon different errors at different parts of the function, or fall through upon success.

All in all, you should use a do-while loop here.




回答5:


People created middle and high level compilers with using assembler(and high-level assembler). Assembler has many jmp jnz jg jl commands act like goto. They made it this far. Cant you do the same? If you can't then you answered your own question.

I cant say the same thing for interpreters.



来源:https://stackoverflow.com/questions/11921071/should-i-avoid-goto-in-situations-like-this

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!