C++: How to Check If the Cin Buffer Is Empty

C++: how do I check if the cin buffer is empty?

When reading from std::cin, it's preferable not to use the stream extraction operator >> as this can have all sorts of nasty side effects. For example, if you have this code:

std::string name;
std::cin >> name;

And I enter John Doe, then the line to read from cin will just hold the value John, leaving Doe behind to be read by some future read operation. Similarly, if I were to write:

int myInteger;
std::cin >> myInteger;

And I then type in John Doe, then cin will enter an error state and will refuse to do any future read operations until you explicitly clear its error state and flush the characters that caused the error.

A better way to do user input is to use std::getline to read characters from the keyboard until the user hits enter. For example:

std::string name;
getline(std::cin, name); // getline doesn't need the std:: prefix here because C++ has ADL.

ADL stands for argument-dependent lookup. Now, if I enter John Doe, the value of name will be John Doe and there won't be any data left around in cin. Moreover, this also lets you test if the user just hit enter:

std::string name;
getline(std::cin, name);

if (name.empty()) {
/* ... nothing entered ... */
}

The drawback of using this approach is that if you want to read in a formatted data line, an int or a double you'll have to parse the representation out of the string. I personally think this is worth it because it gives you a more fine-grained control of what to do if the user enters something invalid and "guards" cin from ever entering a fail state.

I teach a C++ programming course, and have some lecture notes about the streams library that goes into a fair amount of detail about how to read formatted data from cin in a safe way (mostly at the end of the chapter). I'm not sure how useful you'll find this, but in case it's helpful I thought I'd post the link.

Hope this helps!

C++ how to check if the std::cin buffer is empty

As you've discovered, this is a deceptively complicated task. This is because there are multiple issues here at play, both the C++ library, and the actual underlying file.

C++ library

std::cin, and C++ input streams, use an intermediate buffer, a std::streambuf. Input from the underlying file, or an interactive terminal, is not read character by character, but rather in moderately sized chunks, where possible. Let's say:

int n;

std::cin >> n;

Let's say that when this is done and over is, n contains the number 42. Well, what actually happened is that std::cin, more than likely, did not read just two characters, '4' and '2', but whatever additional characters, beyond that, were available on the std::cin stream. The remaining characters were stored in the std::streambuf, and the next input operation will read them, before actually reading the underlying file.

And it is equally likely that the above >> did not actually read anything from the file, but rather fetched the '4' and the '2' characters from the std::streambuf, that were left there after the previous input operation.

It is possible to examine the underlying std::streambuf, and determine whether there's anything unread there. But this doesn't really help you.

If you were about to execute the above >> operator, you looked at the underlying std::streambuf, and discover that it contains a single character '4', that also doesn't tell you much. You need to know what the next character is in std::cin. It could be a space or a newline, in which case all you'll get from the >> operator is 4. Or, the next character could be '2', in which case >> will swallow at least '42', and possibly more digits.

You can certainly implement all this logic yourself, look at the underlying std::streambuf, and determine whether it will satisfy your upcoming input operation. Congratulations: you've just reinvented the >> operator. You might as well just parse the input, a character at a time, yourself.

The underlying file

You determined that std::cin does not have sufficient input to satisfy your next input operation. Now, you need to know whether or not input is available on std::cin.

This now becomes an operating system-specific subject matter. This is no longer covered by the standard C++ library.

Conclusion

This is doable, but in all practical situations, the best solution here is to use an operating system-specific approach, instead of C++ input streams, and read and buffer your input yourself. On Linux, for example, the classical approach is to set fd 0 to non-blocking mode, so that read() does not block, and to determine whether or not there's available input, just try read() it. If you did read something, put it into a buffer that you can look at later. Once you've consumed all previously-read buffered input, and you truly need to wait for more input to be read, poll() the file descriptor, until it's there.

Do stuff with cin buffer until empty

If standard input is attached to a terminal, the program will wait for input until it gets an EOF condition, which you can send on POSIX systems with a Ctrl-D

Safe operation to clear the empty input buffer in C++

Breaking down main:

int main(void)
{
char myStr[50];
cin.ignore (std::numeric_limits<std::streamsize>::max(),'\n');

A bad idea, but you noticed that already. There must be a newline in the stream or you sit and wait for one. If the user's not expecting this behaviour you can expect to wait a long time and have a frustrated user. That's a bad scene.

    cout<<"Enter the String\n";
cin>>myStr;

Also a bad idea, but for a different reason. >> doesn't know it should stop at 49 characters to prevent overflowing myStr. Bad things happen at that 50th character.

    // After reading remove unwanted characters from the buffer
// so that next read is not affected
cin.ignore (std::numeric_limits<std::streamsize>::max(),'\n');

This one is safe. >> won't consume the newline, or any other whitespace and in order for the stream to hand over the data from the console someone must have hit enter and provided a newline.

}

A general rule of thumb is to not ignore unless you have reason to ignore, and if you have reason, ignore right away. Do not wait until before the next stream operation to ignore, be cause what if this operation is the first? Or the previous operation did not leave anything to ignore?. ignore after the operation that left what you want ignored in the stream. So

std::string getfirstword()
{
std::string firstword;
if (std::cin >> firstword)
{
cin.ignore (std::numeric_limits<std::streamsize>::max(),'\n');
return firstword;
}
return ""; // or perhaps
// throw std::runtime_error("There is no first word.");
// is more appropriate. Your call.
}

is good, but

std::string getfirstword()
{
cin.ignore (std::numeric_limits<std::streamsize>::max(),'\n');
std::string firstword;
if (std::cin >> firstword)
{
return firstword;
}
return "";
}

is an offence in the eyes of all that is holy. Don't do it.

As for getline, it gets a line. All of it up to the end of the file or the end of the line, whichever comes first. It also eats the end of the line for you so you don't have to worry about a stray newline harshing your mellow later.

If you only want part of the line, you will have to break it down. Typical usage for this is something along the lines of

std::string line;
if (std::getline(std::cin,line))
{
std::istringstream istr(line);
std::string firstword;
if (istr >> firstword)
{
// do something with firstword
}
else
{
// there is no firstword. Do something else.
}
}

getline reads everything up to and including the newline. It's no longer in the stream, so I'd consider this safe. You don't have to worry about garbage hanging around on the end of the line. You may have to worry about the next line, though.

How can I use cin.get() to detect an empty user input?

I think this code does your job.

#include <iostream>
#include <sstream>

using std::cin;
using std::cout;
using std::string;

int main()
{
string in = "";
int n = 0;
while (true)
{
cout << "Please enter a number: ";
getline(cin, in);
std::stringstream s(in);
if (s >> n)
break;
cout << "Invalid number, please try again\n";
}
return 0;
}

AFAIK, I think that you shouldn't use the get() function to input numbers. The get() function is designed to input characters and it behaves as an Unformatted Input Function. The >> operator seems better for this job. The extraction operator >> will wait for your input if you press the ENTER key because it will ignore it. However, the newline input is stored in the cin input stream. You can also try a combination of the peek() and the seekg() function to check for an existing newline input in the input stream and do what you want.

What exactly empty input means for cin.get()?

If cin.get(input, ArSize); reads no characters (i.e. the first character it encounters is a newline) it calls setstate(failbit) putting the stream into a failed state and therefore while(cin) becomes false, ending the loop.

How do I flush the cin buffer?

Possibly:

std::cin.ignore(INT_MAX);

This would read in and ignore everything until EOF. (you can also supply a second argument which is the character to read until (ex: '\n' to ignore a single line).

Also: You probably want to do a: std::cin.clear(); before this too to reset the stream state.

std::cin: empty the input buffer without blocking

Having a system supporting tcgetattr/tcsetattr:

#include <iostream>
#include <stdexcept>

#include <termios.h>
#include <unistd.h>

class StdInput
{
// Constants
// =========

public:
enum {
Blocking = 0x01,
Echo = 0x02
};

// Static
// ======

public:
static void clear() {
termios attributes = disable_attributes(Blocking);
while(std::cin)
std::cin.get();
std::cin.clear();
set(attributes);
}

// StdInput
// ========

public:
StdInput()
: m_restore(get())
{}

~StdInput()
{
set(m_restore, false);
}

void disable(unsigned flags) { disable_attributes(flags); }
void disable_blocking() { disable_attributes(Blocking); }
void restore() { set(m_restore); }

private:
static termios get() {
const int fd = fileno(stdin);
termios attributes;
if(tcgetattr(fd, &attributes) < 0) {
throw std::runtime_error("StdInput");
}
return attributes;
}

static void set(const termios& attributes, bool exception = true) {
const int fd = fileno(stdin);
if(tcsetattr(fd, TCSANOW, &attributes) < 0 && exception) {
throw std::runtime_error("StdInput");
}
}

static termios disable_attributes(unsigned flags) {
termios attributes = get();
termios a = attributes;
if(flags & Blocking) {
a.c_lflag &= ~ICANON;
a.c_cc[VMIN] = 0;
a.c_cc[VTIME] = 0;
}
if(flags & Echo) {
a.c_lflag &= ~ECHO;
}
set(a);
return attributes;
}

termios m_restore;
};

int main()
{
// Get something to ignore
std::cout << "Ignore: ";
std::cin.get();

// Do something

StdInput::clear();

std::cout << " Input: ";
std::string line;
std::getline(std::cin, line);
std::cout << "Output: " << line << std::endl;

return 0;
}


Related Topics



Leave a reply



Submit