Обработка перегрузки 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
.
Ответы
Ответ 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 и локали С++.