Подтвердить что ты не робот

Обработка перегрузки std:: endl?

Я хочу определить класс MyStream, чтобы:

MyStream myStream;
myStream << 1 << 2 << 3 << std::endl << 5 << 6 << std::endl << 7 << 8 << std::endl;

выводит вывод

[blah]123
[blah]56
[blah]78

В принципе, я хочу, чтобы "[blah]" вставлен спереди, а затем вставлен после каждого бесконечного std::endl?

Трудность здесь - это не логическое управление, а обнаружение и перегрузка обработки std::endl. Есть ли элегантный способ сделать это?

Спасибо!

EDIT: мне не нужны советы по управлению логикой. Мне нужно знать, как обнаружить/перегрузить печать std::endl.

4b9b3361

Ответ 1

Что вам нужно сделать, это написать собственный буфер потока:
Когда буфера потока сброшено, вы выводите префиксные символы и содержимое потока.

Следующее работает, потому что std:: endl вызывает следующее.

1) Добавьте '\n' в поток.
2) Вызов flush() в потоке
2a) Это вызывает pubsync() в буфере потока.
2b) Это вызывает виртуальный метод sync()
2c) Отмените этот виртуальный метод, чтобы выполнить нужную работу.

#include <iostream>
#include <sstream>

class MyStream: public std::ostream
{
    // Write a stream buffer that prefixes each line with Plop
    class MyStreamBuf: public std::stringbuf
    {
        std::ostream&   output;
        public:
            MyStreamBuf(std::ostream& str)
                :output(str)
            {}

        // When we sync the stream with the output. 
        // 1) Output Plop then the buffer
        // 2) Reset the buffer
        // 3) flush the actual output stream we are using.
        virtual int sync ( )
        {
            output << "[blah]" << str();
            str("");
            output.flush();
            return 0;
        }
    };

    // My Stream just uses a version of my special buffer
    MyStreamBuf buffer;
    public:
        MyStream(std::ostream& str)
            :std::ostream(&buffer)
            ,buffer(str)
        {
        }
};


int main()
{
    MyStream myStream(std::cout);
    myStream << 1 << 2 << 3 << std::endl << 5 << 6 << std::endl << 7 << 8 << std::endl;
}

> ./a.out
[blah]123 
[blah]56 
[blah]78
>

Ответ 2

Ваши перегруженные операторы класса MyStream должны установить флаг предыдущей печати-токена-был-endl.

Затем, если следующий объект будет напечатан, [blah] может быть вставлен перед ним.

std::endl - это функция, принимающая и возвращающая ссылку на std::ostream. Чтобы обнаружить, что это было перенесено в ваш поток, вам нужно перегрузить operator<< между вашим типом и такой функцией:

MyStream& operator<<( std::ostream&(*f)(std::ostream&) )
{
    std::cout << f;

    if( f == std::endl )
    {
        _lastTokenWasEndl = true;
    }

    return *this;
}

Ответ 3

Согласился с Нилом по принципу.

Вы хотите изменить поведение буфера, потому что это единственный способ расширения iostreams. endl делает следующее:

flush(__os.put(__os.widen('\n')));

widen возвращает один символ, поэтому вы не можете поместить туда свою строку. put вызывает putc, который не является виртуальной функцией и только время от времени перехватывает overflow. Вы можете перехватить в flush, который вызывает буфер sync. Вам нужно будет перехватить и изменить все символы новой строки, так как они overflow ed или вручную sync ed и преобразуют их в вашу строку.

Конструирование класса буфера переопределения затруднительно, поскольку basic_streambuf ожидает прямого доступа к его буферной памяти. Это не позволяет вам легко передавать запросы ввода-вывода до существующего basic_streambuf. Вам нужно выйти на конечность и предположить, что вы знаете класс буфера потока и извлекаете из него. (cin и cout не гарантировано использовать basic_filebuf, насколько я могу судить.) Затем просто добавьте virtual overflow и sync. (См. § 27.5.2.4.5/3 и 27.5.2.4.2/7.) Выполнение замены может потребовать дополнительного пространства, поэтому будьте осторожны, чтобы выделить это раньше времени.

- ИЛИ -

Просто объявите новый endl в вашем собственном пространстве имен или, лучше, манипулятор, который не называется endl вообще!

Ответ 4

Вместо того, чтобы пытаться изменить поведение std::endl, вы, вероятно, должны создать фильтр streambuf для выполнения задания. Джеймс Канзе имеет пример, показывающий, как вставить метку времени в начале каждой выходной строки. Он должен требовать внесения только незначительных изменений в любой префикс, который вы хотите в каждой строке.

Ответ 5

Я использую указатели функций. Это звучит ужасно для людей, которые не привыкли к C, но в большинстве случаев они намного эффективнее. Вот пример:

#include <iostream>

class Foo
{
public:
    Foo& operator<<(const char* str) { std::cout << str; return *this; }
    // If your compiler allows it, you can omit the "fun" from *fun below.  It'll make it an anonymous parameter, though...
    Foo& operator<<(std::ostream& (*fun)(std::ostream&)) { std::cout << std::endl; }
} foo;

int main(int argc,char **argv)
{
    foo << "This is a test!" << std::endl;
    return 0;
}

Если вы действительно хотите, чтобы вы могли проверить адрес endl, чтобы подтвердить, что вы не получаете какую-либо другую функцию void/void, но я не думаю, что это стоит в большинстве случаев. Надеюсь, что это поможет.

Ответ 6

Вы не можете изменить std::endl - поскольку это имя предполагает, что оно является частью стандартной библиотеки С++, и его поведение исправлено. Вам нужно изменить поведение самого потока, когда он получает конец строки. Лично я бы не подумал, что это стоит усилий, но если вы хотите рисковать в эту область, я настоятельно рекомендую прочитать книгу Стандартные IOStreams и локали С++.