Тег «грабли»

Как отбросить непрочитанные символы из cin?

Зачем отбрасывать символы? Попробуем решить такую задачу: нужно прочитать из cin целое число, причем если вместо целого числа нам подсунут какую-нибудь гадость, нужно сообщить об этом и попытаться прочитать число снова. Попытка номер один:

1
2
3
4
5
6
7
using namespace std;
int value = 0;
while (not (cin >> value)) {
    cout << "Invalid format" << endl;
    cin.clear();
}
cout << "Your input: " << value << endl;

Попытавшись ввести нечто, непохожее на целое число, получим бесконечный цикл. Попытка прочитать число в строке 3 заканчивается неудачей, но введенные символы из буфера ввода никуда не исчезают. Повторное чтение, натыкаясь на те же самые символы, заканчивается с тем же результатом.

Беглый гуглеж приводит меня к функции istream::sync(), которая, как говорит один источник, делает именно то, что нам нужно:

Synchronizes the buffer associated with the stream to its controlled input sequence. This effectively means that the unread characters in the buffer are discarded.

Что ж, попробуем:

1
2
3
4
5
6
7
8
using namespace std;
int value = 0;
while (not (cin >> value)) {
    cout << "Invalid format" << endl;
    cin.clear();
    cin.sync();
}
cout << "Your input: " << value << endl;

Хм. В моей системе результат не изменился, код работает точно так же, как и первый. Беда в том, что функция istream::sync() в некоторых реализациях стандартной библиотеки таки отбрасывает непрочитанные символы. Но не в моем случае.

А я человек простой — лезу в код своей стандартной библиотеки и смотрю что да как. Выходит, что istream::sync() вызывает stdio_sync_filebuf::pubsync(), которая вызывает stdio_sync_filebuf::sync(), которая, наконец, вызывает fflush(stdin). Понятно, что fflush(stdin) не может оказать никакого влияния на входной буфер cin.

Так что упомянутый выше «один источник» не стоит считать истиной в последней инстанции. Но надо сказать, что попытки найти истину в стандарте — еще более неблагодарное занятие.

В общем, правильное решение заключается в использовании функции istream::ignore() таким вот образом:

1
2
3
4
5
6
7
8
using namespace std;
int value = 0;
while (not (cin >> value)) {
    cout << "Invalid format" << endl;
    cin.clear();
    cin.ignore(numeric_limits<streamsize>::max(), '\n');
}
cout << "Your input: " << value << endl;

Надо отметить, что вызов ignore() неплохо бы вставлять даже после успешного ввода, так как при вводе строки «123qwe» первые три символа будут прочитаны и распознаны как число, а «qwe» останутся во входном буфере и будут портить жизнь при следующем чтении. Еще одна тонкость — обработка конца файла. Если пользователь в ответ на приглашение к вводу нажмет Ctrl+D или что там в вашей системе для этого предназначено, то программа вместо того, чтобы завершиться, сообщит о неверном формате и предложит ввести число еще раз. Поэтому по-хорошему код должен быть вроде этого (вся программа):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <limits>
using namespace std;
template <typename Type>
bool read_value(const std::string &prompt, Type &value)
{
    bool repeat = true;
    bool eof = cin.eof();
    while (repeat and not eof) {
        cout << prompt << ": ";
        cin >> value;
        eof = cin.eof();
        repeat = cin.fail() and not eof;
        if (repeat) {
            cout << "***Invalid data format!" << endl;
            cin.clear();
        } else if (eof) {
            cout << "(EOF)" << endl;
        }
        cin.ignore(numeric_limits<streamsize>::max(), '\n');
    }
    return not eof;
}
int main()
{
    int value = 0;
    while (read_value("Input integer", value))
        cout << "Your input: " << value << endl;
}

Но и в этом, казалось бы, безошибочном, коде затаилась пара граблей. Например, что если функцию read_value() использовать для ввода символов или строк? Сумеем мы ввести символ пробела? Сумеем ли ввести строку из нескольких слов, разделенных пробелами? Хрен! С вводом символа пробела еще несложно — надо только использовать манипулятор noskipws, а вот ввод строк с пробелами потребует перегрузки read_value() и использования getline() вместо оператора >>.

В общем, писать на C++ — сплошное счастье.
УжасноПлохоНормальноХорошоОтлично (1 голосов, средний: 5,00 из 5)
Loading ... Loading ...

Unix: тени прошлого

Друзья, не обижайтесь, но у меня сейчас совершенно нет времени писать что-то вдумчивое и основательное. Поэтому решил разбавить тишину ссылками на статьи с LWN:

Вкратце о содержимом. В ранние годы Unix были приняты некоторые архитектурные решения, которые определили то, что сейчас принято называть «Unix Way». Над этими решениями думали не самые глупые люди, но предусмотреть, что будет через 40 лет, никто не в силах. Поэтому в свете современных требований некоторые ключевые решения и паттерны до сих пор остались красивыми и согласованными, а некоторые породили проблемы разной величины. В статьях как раз про второй тип: какие выявились проблемы, как их можно решать и как они решаются сейчас.

Лично меня статьи зацепили тем, что многие из описанных проблем я заметил и сам, хотя и не подвергал их такому глубокому анализу.

Как часто бывает на LWN, комменты не менее ценны, чем сами статьи.

Если XOrg падает при запуске Qt-приложений

А именно, XOrg 1.9 и несколько мониторов, сконфигурированные с помощью Xinerama, приводят к стабильному падению Qt- и некоторых Tk-приложений. Это подтвержденный баг. К сожалению, часто в репозиториях популярных дистрибутивов нужные фиксы появляются очень неторопливо, а жить с таким багом ну никак невозможно.

Исправляется баг элементарно: нужно накатить соответствующий патч на исходники иксов и пересобрать. Для арчеводов на форуме приведена подробная инструкция (подразумевает использование Arch Build System).

Обратите внимание на феерический комментарий к патчу:

This fixes a typo introduced in commit 80b5d3a3264d2c5167e5ac85a3b04af0f89cece1. The pointer pDst was changed unintentionally to pWin from a copy/paste error. This resulted in all QT-based apps and some tcl/tk ones (like fontforge) to crash X 1.9 on starting up, when Xinerama was enabled.

То есть, некто внес в код XOrg критическую ошибку в результате бездумного применения copy/paste. Для проекта такого уровня — эпический фэйл.

cm-super и Fedora 12

Почему-то в репозиториях Fedora 12 отсутствует замечательный пакет Type1-шрифтов cm-super. Так как жизни я себе без него не представляю, то пришлось брать соответствующий пакет на CTAN и ставить вручную. Суть поста в том, что идущие в комплекте с cm-super инструкции по установке неверны. Начиная с некоторого времени стандартные пути к .map- и .enc-файлам в TeX Live были изменены, о чем сказано в официальном предупреждении.

Для лентяев добрые люди сочинили установочный скрипт (внизу страницы).

УжасноПлохоНормальноХорошоОтлично (Еще не оценили)
Loading ... Loading ...

Гугл-убийца

Гугл меня сегодня чуть не убил, натурально. У меня на главной странице виджет с погодой. Смотрю утром: +4?. Ну, думаю, опять синоптики облажались со своими морозами. Оделся соответственно погоде, по-осеннему. Приезжаю на работу — а Гугл показывает уже -18?. Тут-то я и понял, почему мне по дороге казалось, что как-то несколько прохладно для четырех градусов…

К сожалению, скриншот сделать не смог.

CiteULike и асфальтовый каток

Наверное, искусственному разуму в недрах CiteULike не понравились мои легкомысленные высказывания в его адрес, и он решил мстить… Или просто у разработчиков руки смонтированы в неправильное место? Сейчас разберемся.

Сначала я загрузил туда книгу: создал запись, ввел библиографическую информацию, а потом и залил к ним на сервер PDFку. Не прошло и получаса, как запись об этой книге исчезла из моей тамошней личной библиотеки, как будто ее и не было. Первое подозрение у меня было на копирастов: небось, углядели в PDF-файле нарушение авторских прав, и стерли без суда и следствия.

Я так просто не сдался и продолжил эксперимент. Пошел на CiteSeerX и нашел там общедоступную статью, которую и залил на CiteULike (кстати, шикарнейшая фича — автоматический экспорт библиографических данных с CiteSeerX и других подобных сайтов). Теперь у копирастов не было повода. Но через час статья все равно исчезла.

Было особенно обидно, что исчезновение не сопровождалось никакими спецэффектами вроде клубов дыма, воя сирен или хотя бы надписи в профиле «статья такая-то удалена». Я согласен даже на отсутствие объяснения причин (это позволяет предположить, что я чего-то недопонял), но какое-то уведомление должно быть! Нет, раздел «Library» нагло врет: «You haven’t added any articles to your library yet».

Еще одна статья, закинутая в порыве последней надежды, сумела пережить ночь, после чего отправилась вслед за своими почившими товарищами. Черная дыра прям, даже заходить теперь туда страшно. Вдруг и меня засосет? И не останется от меня ничего, только надпись у жены в паспорте: «You haven’t married any man yet». Стивен Кинг отдыхает.

Теперь серьезно. CiteULike совершил вопиющее, непростительное преступление. Я потратил время на ввод данных, а мои данные были целенаправленно удалены. Неуважение ко времени пользователя — это смертный грех для любого софта. Заблокируйте данные, запретите скачивание, но удалять созданное пользователем без его согласия — нельзя.

Другое преступление, не менее тяжелое, — молчание в экстренной ситуации. Большая удача, что стерлись первые же добавленные статьи. А что если бы я туда напихал 400 статей, а потом некоторые из них взяли бы и исчезли? Я мог не заметить потери важной информации. Замалчивание таких случаев исключает возможность доверия. Подвел сейчас — значит, может подвести и потом.

Нет, я не буду писать разработчикам. К ним применима та же логика. Придется все же браться за дело самому.

УжасноПлохоНормальноХорошоОтлично (1 голосов, средний: 5,00 из 5)
Loading ... Loading ...