#c #position #ostream
#c #позиция #ostream
Вопрос:
В C , как мне определить, находится ли my std::ostream os
в начале строки, другими словами (я думаю), самая последняя вещь, записанная в os
os<<'n'
, — это или os<<std::endl()
, или, иначе, ничего еще не было записано os
?
На первый взгляд это звучит ненужно, потому что я могу просто отслеживать состояние самостоятельно. Но распространенным сценарием является следующий, когда отслеживание будет включать изменение каждого os<<thing
оператора, который может быть вызван из try
блока, очень неестественным образом.
try {
do_something_which_writes_to(std::cout);
}
catch(const My_erroramp;error) {
print_a_newline_if_necessary(std::cout);
std::cout<<error<<"n";
}
(На самом деле, конечно, мы хотим записать error
в std::cerr
, но это обычно смешивается с std::cout
, если только один из них не перенаправлен, поэтому мы все равно хотим завершить std::cout
строку перед печатью в std::cerr
. Я намеренно упростил пример, чтобы избежать этого отвлечения.)
Вы можете себе представить, что os.tellp()
это был бы ответ, но tellp()
, похоже, работает только на std::ofstream
. По крайней мере, std::cout.tellp()
для меня всегда возвращается -1
, указывая, что он не поддерживается.
Комментарии:
1.
cout
возможно, вы даже не знаете, что было записано в буфер. Это могло быть записано на базовый носитель и давно исчезло. Вам это может сойти с рук при работе с файлом, потому что файл (вероятно) никуда не денется, пока вы его не закроете. Последовательный порт, сетевой сокет или что-то еще поддерживает командную консоль, Crom знает только, куда отправились данные и как давно.
Ответ №1:
По крайней мере, когда я читаю вещи, то, что вы действительно хотите, это не возможность получить позицию в текущей строке. Скорее, то, что вы действительно хотите, это иметь возможность печатать что-то, что гарантированно находится в начале строки — текущую строку, если непосредственно предыдущий символ был новой строкой (и, я бы предположил, также, если это был возврат каретки), но в противном случае выводите новую строку, а затем все, что следует.
Вот некоторый код для этого:
#include <iostream>
class linebuf : public std::streambuf
{
std::streambuf* sbuf;
bool need_newline;
int sync() {
return sbuf->pubsync();
}
int overflow(int c) {
switch (c) {
case 'r':
case 'n': need_newline = false;
break;
case 'v':
if (need_newline) {
need_newline = false;
return sbuf->sputc('n');
}
return c;
default:
need_newline = true;
break;
}
return sbuf->sputc(c);
}
public:
linebuf(std::streambuf* sbuf)
: sbuf(sbuf)
, need_newline(true)
{}
std::streambuf *buf() const { return sbuf; }
~linebuf() { sync(); }
};
class linestream : public std::ostream {
linebuf buf;
std::ostream amp;os;
public:
linestream(std::ostreamamp; out)
: buf(out.rdbuf())
, std::ios(amp;buf)
, std::ostream(amp;buf)
, os(out)
{
out.rdbuf(amp;buf);
}
~linestream() { os.rdbuf(buf.buf()); }
};
void do_stuff() {
std::cout << "vMore outputv";
}
int main() {
{
linestream temp(std::cout);
std::cout << "noutputn";
std::cout << "voutput";
do_stuff();
std::cout << "voutputn";
std::cout << "voutputv";
}
std::cout << "voutputv";
}
Поскольку он почти никогда не используется иначе, я перехватил вертикальную вкладку ( 'v'
), чтобы обозначить особое поведение.
Чтобы использовать его, вы просто создаете временный объект типа linestream
(извините, я слишком устал, чтобы прямо сейчас придумать хорошее имя), передавая ему объект ostream, который получит новое поведение при v
записи в него. Когда этот временный объект выходит за пределы области видимости, поток будет восстановлен к своему первоначальному поведению (я сомневаюсь, что кто-то использует v
его достаточно часто, чтобы заботиться, но кто знает, может быть, кому-то не все равно — в любом случае, это просто побочный эффект очистки после себя).
В любом случае, специальное поведение остается на месте при do_stuff
вызове, поэтому оно не просто локально для функции, в которой создается локальный linestream
объект, или что-то в этом роде — после его создания специальное поведение остается в силе до тех пор, пока оно не будет уничтожено.
Однако еще один момент: когда / если вы смешиваете выходные данные из cout
и cerr
, это не сильно поможет. В частности, ни один из них не будет вообще осведомлен о состоянии другого. Вероятно, вам очень понадобятся некоторые подключения к выходному терминалу (или что-то в этом роде), чтобы иметь возможность справиться с этим, поскольку перенаправление вывода обычно обрабатывается ОС, поэтому внутри программы невозможно даже угадать, записываются ли данные cout
в одно и cerr
то же место или нет.
Комментарии:
1. Большое вам спасибо за урок — вы проделали огромную работу, и я всегда боялся углубляться в
std::streambuf
него и его друзей. Кстати, я думаю, что конструктор может захотеть инициализироватьneed_newline(false)
. Но я подожду, если есть способ использовать существующийstd::ostream
API, прежде чем отмечать это как принятый ответ.2. @AdamChalcraft: должно ли оно изначально быть true или false, зависит от того, что вы хотите (возможно, я недостаточно внимательно читал, но я не видел четкого определения того, что вы хотели в начале вывода. Но в любом случае, вы видите, чтобы понять, как заставить его делать то, что вы хотите…
3. Что касается
cout
иcerr
вещи, я согласен, что идеального ответа нет, но я доволен{std::cout<<"v"; std::cerr<<"verrorn";}
решением. В худшем случае он записывает и дополнительно'n'
std::cout
в ситуацию, когда ошибка все равно произошла.