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