Перегрузка обработки std :: endl? - PullRequest
30 голосов
/ 06 февраля 2010

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

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

дает вывод

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

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

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

Спасибо!

РЕДАКТИРОВАТЬ: Мне не нужен совет по логическому управлению. Мне нужно знать, как обнаружить / перегрузить печать std::endl.

Ответы [ 6 ]

31 голосов
/ 06 февраля 2010

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

Следующее работает, потому что 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)
            {}
            ~MyStreamBuf() {
                if (pbase() != pptr()) {
                    putOutput();
                }
            }

        // 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() {
            putOutput();
            return 0;
        }
        void putOutput() {
            // Called by destructor.
            // destructor can not call virtual methods.
            output << "[blah]" << str();
            str("");
            output.flush();
        }
    };

    // 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
>
18 голосов
/ 06 февраля 2010

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

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

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

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

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

    return *this;
}
1 голос
/ 08 апреля 2012

Я использую указатели функций. Это звучит ужасно для людей, которые не привыкли к 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-функции, но я не думаю, что это стоит в большинстве случаев. Я надеюсь, что это помогает.

1 голос
/ 06 февраля 2010

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

1 голос
/ 06 февраля 2010

Принципиально согласился с Нилом.

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

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

widen возвращает один символ, поэтому вы не можете вставить туда свою строку. put вызывает putc, который не является виртуальной функцией и лишь изредка подключается к overflow. Вы можете перехватить на flush, который вызывает буфер 1011 *. Вам нужно будет перехватить и изменить все символы новой строки, как они 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!

0 голосов
/ 06 февраля 2010

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

...