6

For a project I am working on, I need the program to be able to receive input from the user, but while they are inputting something, the program can continue in the loop.

For example:
while (true)
{
    if (userInput == true)
    {
        cin >> input
    }
    //DO SOMETHING
}

This would mean that //DO SOMETHING would happen every loop, without the user pressing enter a million times.

Before, my solution was creating my own input using kbhit() and getch() from conio.h, but that got very messy, and I don't like using conio.h for portability reasons etc. Also, it doesn't need to use cin specifically, because there is a good chance it just wouldn't work with it, so any good solution that doesn't require me making my own input with a 'not very good' library, would be much appreciated.
Community
  • 1
  • 1
Ben Hollier
  • 585
  • 2
  • 11
  • 32
  • 2
    Maybe you need some kind of asynchronous I/O? Like with epoll or kqueue, or libevent2? – Kerrek SB Dec 20 '14 at 17:21
  • Do you want the loop to stop when the user presses the first key, or when the input is complete? – Wintermute Dec 20 '14 at 17:28
  • Basically, I don't want the input to affect the program at all, so that the loop is continuous, but every so often the system gets new data to work with, in the form of an input – Ben Hollier Dec 20 '14 at 17:38
  • 1
    possible duplicate of [Non-blocking console input C++](http://stackoverflow.com/questions/6171132/non-blocking-console-input-c) – Cornstalks Dec 20 '14 at 18:17

4 Answers4

3

It could be worth looking into multi-threading for this. I'm usually hesitant to suggest it, because multithreading pulls in a host of potential problems that can end up difficult to debug, but in this case they can be isolated fairly easily. I envision something like this:

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>

int main() {
  std::atomic<bool> interrupted;
  int x;
  int i = 0;

  do {
    interrupted.store(false);

    // create a new thread that does stuff in the background
    std::thread th([&]() {
        while(!interrupted) {
          // do stuff. Just as an example:
          std::cout << i << std::flush;
          std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
      });

    std::cin >> x;

    // when input is complete, interrupt thread and wait for it to finish
    interrupted.store(true);
    th.join();

    // apply x, then loop to make new thread to do stuff
    i = x;
  } while(x != -1); // or some other exit condition
}

At first glance this looks somewhat wasteful because it keeps spawning and throwing threads away, but user input takes, in computing terms, an eternity, so the overhead should not be prohibitive. More importantly, it does have the advantage of avoiding any suggestion of data races wholesale because the only means of communication between the main (input) loop and the background thread is the atomic interruption flag, and the application of x to shared data happens when no thread is running that could race the main loop.

Wintermute
  • 42,983
  • 5
  • 77
  • 80
  • While this solution looks promising, according to some other places, gcc doesn't support std::thread. Unless I have my compiler set up wrong (which is Code::Blocks in case anyone can help) – Ben Hollier Dec 20 '14 at 18:14
  • gcc has supported `std::thread` for some time now; I've tested it here with gcc 4.9 under Linux. But if you need to support very old gccs, there's always [Boost.Thread.](http://www.boost.org/doc/libs/1_57_0/doc/html/thread.html) The standard library threads ware largely based on it, so it'll work very nearly the same way. EDIT: Perhaps you have to configure codeblocks to pass g++ the `-std=c++11` option? – Wintermute Dec 20 '14 at 18:17
  • @Ben did you use -std=c++11 compiling option ? – Christophe Dec 20 '14 at 18:21
  • @Christophe yes, in fact I tried all 3 options in the Compiler settings – Ben Hollier Dec 20 '14 at 18:30
  • @Wintermute I understand from OP that he wanted to do something even if the uer wasn't typing in some input. Wo why recreate the theread in each do-iteration, and not just starting it before the starting the loop ? – Christophe Dec 20 '14 at 18:31
  • Because it's easier. This approach uses the crowbar approach against data races; I expect the main loop and background thread will end up sharing data, and this way works without mutexing (which is easy to get wrong) for what I expect to be negligible cost. But yes, you could let the thread run longer and synchronize through other means. – Wintermute Dec 20 '14 at 18:34
  • @Wintermute Code::Blocks version 13.12 apparently – Ben Hollier Dec 20 '14 at 18:39
  • That comes with gcc 4.7, I believe. I can't test it with mingw right now, but here the code and gcc 4.7 work with the -pthread -std=c++11 options after I replace the `sleep_for` call with a call to `usleep` (gcc 4.7 doesn't appear to have `std::this_thread::sleep_for` yet) – Wintermute Dec 20 '14 at 18:44
  • @Wintermute I changed the compiler flags to '-pthread -std=c++11', but to no avail – Ben Hollier Dec 20 '14 at 18:48
  • Hmmm...I did some digging, and it appears that MinGW had some trouble getting the threading stuff in C++11 to work (or didn't care a lot; it's unclear which). There's a [fork](http://mingw-w64.sourceforge.net/) where this supposedly works that you could swap in, but other than that I suppose I can only point you at Boost.Thread again. Bummer. – Wintermute Dec 20 '14 at 19:02
  • @Wintermute I'm trying that now, hopefully this will all be worth it – Ben Hollier Dec 20 '14 at 19:11
  • @Wintermute: The only thing that troubles me with this solution, is that in the absence of input... the program never ends. I suppose it's not a big downside, and to be honest I have no better idea :x – Matthieu M. Dec 21 '14 at 11:48
2

Disclaimer: the following seems to be working with gcc on Linux, however for some reasons it does not work with VC++ on Windows. The specifications appear to give a lot of leeway to the implementations here, and VC++ definitely takes advantage of it...

There are multiple functions available on any std::basic_istream or its underlying std::basic_streambuf.

In order to know if there is any character available for input, you may call in_avail on std::basic_streambuf:

if (std::cin.rdbuf() and std::cin.rdbuf()->in_avail() >= 0) {
}

in_avail gives you the number of characters available without blocking, it returns -1 if there is no such character. Afterward, you can use the regular "formatted" read operations such as std::cin >> input.

Otherwise, for unformatted reads, you can use readsome from std::basic_istream, which returns up to N characters available without blocking:

size_t const BufferSize = 512;
char buffer[BufferSize];

if (std::cin.readsome(buffer, BufferSize) >= 1) {
}

However it is noted that the implementation of this method is highly variable, so for a portable program it might not be that useful.

Note: as mentioned in the comment, the in_avail approach might be spotty. I confirm it can work, however you first have to use an obscure feature of C++ IO streams: std::ios_base::sync_with_stdio(false) which allows C++ streams to buffer input (and thus steal it from C's stdio buffers).

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 2
    This does not work portably. With gcc's libstdc++, `std::cin.rdbuf()->in_avail()` always returns 0 (unless you swap in your own stream buffer). – Wintermute Dec 20 '14 at 17:33
  • 2
    None of the solution works on visual studio: with `in_avail()` it's always true and `readsome()` returns always 0. – Christophe Dec 20 '14 at 17:46
  • @Wintermute: I'd like to say "interesting", but at this point it's not even funny, but I should have thought about it. For `in_avail` the issue is solved by using `sync_with_stdio`, I added that as an answer. – Matthieu M. Dec 20 '14 at 18:46
  • @Christophe: I am not even sure I want to know why, you may try the `sync_with_stdio` trick on VC++ (I don't have it), it may solve the issue... – Matthieu M. Dec 20 '14 at 18:47
  • @MatthieuM. Désollé, but it still doesn't work ! If no chars are available it always returns 0. If I change the test to `>0` instead of `>=0`, it remains 0 even if I type something. It seems that as long as no input is requested from user, the number of available chars doesn't get updated. I'lm not spotty, but I observe that it's not a portable solution. – Christophe Dec 20 '14 at 19:03
  • @Christophe: you are spotty! :) It does not matter though, you are right that this is not portable. Have you tried "priming the pump"? That is, making a first blocking input and then making non-blocking calls? If this still does not work, then the only other solution I can think of is GUI basics: blocking work is delegated to another thread, which communicates its results. It'll be basic, but it really seems a shame to go multi-threading just for that... – Matthieu M. Dec 20 '14 at 19:24
  • @MatthieuM. Even after priming the pump it doesn't work. But I agree with you: it's a pitty that there is no portable standard way to do such a simple thing than testing if there's input on cin ! – Christophe Dec 20 '14 at 19:36
1

It's sad that there is no simple portable way to checking asynchronously if a key was hit. But I guess that the standard committee has carefully evaluated the pros and cons.

If you don't want to rely on third party event management libraries, and if multithreading would be overkill, one alternative could be to have your own version of kbhit(), with conditional compiling for the environments you want to support:

  • if your conio.h supports kbhit() just use it.
  • for windows, you can refer to _kbhit()
  • for linux and posix, you can use Matthieu's answer, or look here for Morgan Mattews's code

It's not the most academic answer, but it's pragmatic.

Christophe
  • 68,716
  • 7
  • 72
  • 138
0

Maybe, you can have a try:

while (true)
{
    if (userInput == true)
    {
        if(cin >> input){
        }else{
            std::cout << "Invalid input!" << std::endl;
            cin.clear();
        }
    }
    //DO SOMETHING
}
tinyzqh
  • 11
  • 1