Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Yet another C++ cin loop issue

Tags:

c++

Update

I found the cause of the problem. I have been experimenting with the fish shell. I saw the comment that said someone had successfully run my code on a Mac, and decided to try it in a standard bash shell. It worked perfectly. So, no more fish shell I guess. :)

I would still appreciate knowing how and why cin works the way it does. This is the main part of my question.


I have run into a popular problem: using cin in a loop. The code is straightforward, but I cannot find a solution. The answers Google have provided me are varied, but usually involve some combination of cin.clear(), cin.ignore(), and cin.get(). Unfortunately, I haven't been able to find a combination or ordering of those that fix my problem. Moreover, I find it frustrating that I don't have a complete understanding of the function of cin. I'd rather not use trial and error to fix this. I want to understand exactly what's going on.

What Should Happen

When I run my code, I should see a prompt with a list of options. I should be able to type a character which will run one of the options. Then, it should display the prompt again and repeat the process until I select the Exit option.

What Actually Happens

As soon as I run the code, it prints the prompt to the screen an arbitrary number of times and eventually stops halfway through the prompt. Then I am unable to do anything but kill it with ^C.

$ ./run

Choose an option:
[A]dd a score
[R]emove a player
[E]xit

    : That is not a valid input.

[repeated a bunch of times]

Choose an option:
[A]dd a score
[R]emove a player
[E]xit

    : That is not a valid input.


Choose ^C
$ 

My Question

What causes cin to do that? As an experienced Java developer (but a beginner C++ developer), I'm familiar with the concepts of buffers and streams and such, but I have no idea how cin works. I know that cin.clear() clears an error state, and cin.ignore() ignores a number of characters in the stream. My Google-fu has thus far been unable to find a concise reference.

Why does cin act the way it does? How should one visualize what happens under the hood when using cin in a loop? What is the most elegant way to implement this infinite menu idea in C++?

My Code

Here is a simplified version of my code, which produces the exact same problem as the full version:

#include <iostream>

using namespace std;

int main () {

    //infinite menu 
    char input;
    while(true) {

        //prompt
        cout << "\n\nChoose an option:\n";
        cout << "[A]dd a score\n";
        cout << "[R]emove a player\n";
        cout << "[E]xit\n";
        cout << "\n\t: ";

        //input
        cin >> input;

        //decide what the input means
        switch(input) {
            case 'a':
            case 'A':
                cout << "Add a score.\n";
                break;
            case 'r':
            case 'R':
                cout << "Remove a player.\n";
                break;
            case 'e':
            case 'E':
                cout << "Program Complete.\n";
                return 0;
                break;
            default:
                cout << "That is not a valid input.\n";
        }
    }
    return 0;
}

I compile and run with:

$ g++ Test.cpp -o run
$ ./run

I'm running gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00) on Mac OS X 10.8.2.

like image 904
Griff George Avatar asked Oct 30 '12 23:10

Griff George


1 Answers

Do yourself a favour and don't extract tokens directly from std::cin. Instead, read line by line with getline and then interpret each line. Also, you must always evaluate the result of an input operation in a boolean context, or you will not be able to handle arbitrary input correctly.

For a test, your program must survive if you call echo "abc" | ./run. This should always be one of your first tests.

Now, on to the code:

#include <string>
#include <iostream>

int main()
{
    for (std::string line; std::getline(std::cin, line); )
    {
        if (line.empty()) { continue; }


        if (line == "A" || line == "a") { /* ... */ continue; }

        if (line == "R" || line == "r") { /* ... */ continue; }

        if (line == "E" || line == "e") { break; }

        std::cout << "Sorry, I did not understand.\n";
    }

    std::cout << "Goodbye!\n";
}
like image 140
Kerrek SB Avatar answered Oct 03 '22 13:10

Kerrek SB