Как буферизуется std::iostream?

Общий вариант использования

Я пытаюсь реализовать базовую оболочку.

Описание

Мне нужно читать пользовательский ввод, пока не будут нажаты некоторые разделители, чтобы можно было выполнить соответствующее действие. Этим разделителем может быть одна буква «а», одна буква «б» или одна буква «с».

Пример ввода будет выглядеть так (где > — приглашение оболочки):

> 111-222-333-444a
Ok, '111-222-333-444' entered

Почему мне нужен встроенный разделитель вместо разделителя новой строки?

Потому что я хотел бы прослушать событие клавиатуры, такое как «стрелка вверх», чтобы стереть текущую команду и распечатать последнюю команду (реализуя функцию истории).

Потому что я хотел бы прослушать событие клавиатуры, такое как «табуляция», для автоматического завершения текущей команды (реализация функции автозаполнения).

Что у меня есть до сих пор

До сих пор мой код выглядит так:

bool done = false;
char c;
while (!done && std::cin.get(c))
{   
    switch (c)
    {
    case 'a':
        // Do something corresponding to 'a'
        done = true;
        break;

    case 'b':
        // Do something corresponding to 'b'
        done = true;
        break;

    case 'c':
        // Do something corresponding to 'c'
        done = true;
        break;

    default:
        // buffer input until a delimiter is pressed
        break;
    }
}

Однако цикл, похоже, выполняется только после нажатия клавиши «новая строка». Такое поведение убивает интерактивную сущность пользовательского ввода.

В чем вопрос?

Я знаю, что std::ostream буферизуется, поэтому содержимое не записывается на диск до тех пор, пока не произойдет какое-то событие, но как насчет std::istream. Это буферизовано? Если да, то как и как обойти это поведение?

Кроме того, я пометил этот вопрос как «домашнее задание», потому что, даже если это не школьное упражнение, это упражнение, которое я пытаюсь выполнить сам, и я не хочу выбирать только библиотеку, которая реализует все это. материал.


person authchir    schedule 31.03.2012    source источник
comment
Вопрос очень хорошо описан, с четкой целью, обоснованием и примером кода. Хотелось бы, чтобы больше людей потратили на это время.   -  person Christian Garbin    schedule 31.03.2012


Ответы (2)


Если вы работаете в операционной системе POSIX, вы можете настроить терминал на небуферизацию, используя функции и структуры, объявленные в termios.h. В основном вам нужно отключить канонический ввод и настроить терминал для неканонического режима. Вот несколько ссылок, которые помогут вам понять разницу между двумя режимами терминала:

  1. #P2# <блочная цитата> #P3#
  2. #P4# <блочная цитата> #P5#
  3. #P6# <блочная цитата> #P7#

По сути, проблема, с которой вы сталкиваетесь, связана не с самим интерфейсом C++ iostream, а скорее с тем, как был настроен управляющий терминал, с которого считывается интерфейс C++ iostream. Таким образом, использование преимущества небуферизованного ввода-вывода будет операцией, зависящей от платформы, и будет различаться в зависимости от того, используете ли вы Windows или фактическую платформу, совместимую с POSIX (включая среды POSIX для Windows, такие как Cygwin).

Если вы обнаружите, что возиться с настройками терминала слишком сложно, вы также можете изучить кроссплатформенную библиотека программирования curses, такая как PDCurses, которая абстрагирует большинство сложностей базовые типы терминалов.

person Jason    schedule 31.03.2012
comment
+1 Это функция буферизации терминала - к iostream напрямую ничего не относится. - person ; 31.03.2012
comment
Верно, это очень зависит от платформы. Я работаю на мейнфрейме с десятками тысяч подключенных терминалов. Просто нельзя заморачиваться каждым нажатием клавиши на каждом терминале. - person Bo Persson; 31.03.2012

Ответы на непосредственные вопросы о том, буферизуется ли и как std::istream, следующие: да, std::istream — это буфер, использующий класс, производный от std::streambuf, который определяет фактический подход к буферизации и чтению для конкретного источника (или, при использовании std::ostream для назначения). Действительно ли это делает какую-либо буферизацию, зависит от этого конкретного класса, и его работы, как правило, нельзя избежать.

Тем не менее, это не ваша проблема! Проблема в том, что обычно ввод не отправляется на стандартный ввод, если программа работает до тех пор, пока не будет нажата клавиша новой строки. Это сделано для того, чтобы некоторое редактирование строк могло быть выполнено реализацией терминала и не должно выполняться каждой программой. К сожалению, нет портативного подхода, чтобы изменить это. В POSIX вы можете перевести стандартный поток ввода (используя файловый дескриптор 0) в неканонический режим, используя tcgetattr() и tcsetattr(). Я не знаю, как добиться этого в системах, отличных от POSIX.

person Dietmar Kühl    schedule 31.03.2012