Обработка перегрузки std::endl?
-
19-09-2019 - |
Вопрос
Я хочу определить класс 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
.
Решение
Что вам нужно сделать, это написать свой собственный буфер потока:
Когда буфер потока очищается, вы выводите символы префикса и содержимое потока.
Следующее работает, потому что std::endl вызывает следующее.
1) Добавьте '
' в поток.
2) Вызывает флеш() в потоке
2a) Это вызывает pubsync() в буфере потока.
2б) Это вызывает виртуальный метод 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
класс должен установить флаг предыдущего напечатанного токена-был-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;
}
Согласен с Нилом в принципе.
Вы хотите изменить поведение буфера, потому что это единственный способ расширить iostreams. endl
Означает ли это:
flush(__os.put(__os.widen('\n')));
widen
возвращает один символ, поэтому вы не можете поместить туда свою строку. put
звонки putc
которая не является виртуальной функцией и лишь изредка подключается к overflow
.Вы можете перехватить в flush
, который вызывает буфер sync
.Вам нужно будет перехватить и изменить все символы новой строки, как они есть. overflow
редактировать или вручную 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
совсем!
Вместо того, чтобы пытаться изменить поведение std::endl
, вам, вероятно, следует создать фильтрующий поток потока, чтобы выполнить эту работу.У Джеймса Канзе есть пример показано, как вставить временную метку в начало каждой выходной строки.Чтобы изменить его на любой префикс, который вы хотите в каждой строке, потребуется лишь небольшая модификация.
Я использую указатели на функции.Это звучит устрашающе для людей, которые не привыкли к 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, но я не думаю, что в большинстве случаев оно того стоит.Надеюсь, это поможет.
Вы не можете изменить std::endl
- как следует из названия, это часть стандартной библиотеки C++, и ее поведение фиксировано.Вам нужно изменить поведение самого потока, когда он получает конец строки.Лично я бы не подумал, что это того стоит, но если вы хотите заняться этой областью, я настоятельно рекомендую прочитать книгу. Стандартные потоки ввода-вывода C++ и локали.