Перегрузка обработки 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

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

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

Спасибо!

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

6 ответов

Решение

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

Следующее работает, потому что 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
>

Ваши перегруженные операторы 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;
}

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

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

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

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

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

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

Создание класса переопределенного буфера проблематично, потому что 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 совсем!

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

Итак, я узнал, как написать собственный манипулятор, что совсем несложно:

#include <sstream>
#include <iostream>

class log_t : public std::ostringstream
{
    public:
};


std::ostream& custom_endl(std::ostream& out)
{
    log_t *log = dynamic_cast<log_t*>(&out);
    if (log)
    {
        std::cout << "custom endl succeeded.\n";
    }
    out << std::endl;
    return out;
}

std::ostream& custom_flush(std::ostream& out)
{
    log_t *log = dynamic_cast<log_t*>(&out);
    if (log)
    {
        std::cout << "custom flush succeeded.\n";
    }
    out << std::flush;
    return out;
}

int main(int argc, char **argv)
{
    log_t log;

    log << "custom endl test" << custom_endl;
    log << "custom flush test" << custom_flush;

    std::cout << "Contents of log:\n" << log.str() << std::endl;
}

Вот результат:

custom endl succeeded.
custom flush succeeded.
Contents of log:
custom endl test
custom flush test

Здесь я создал два нестандартных манипулятора, один из которых endl и тот, который обрабатывает flush. Вы можете добавить любую обработку к этим двум функциям, поскольку у вас есть указатель на log_t объект.

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

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

Другие вопросы по тегам