问题
I was making a "concatenating iterator", i.e. an iterator that would iterate over the int
s 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