Находится ли ostream C в начале строки?

#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 в ситуацию, когда ошибка все равно произошла.