如何为灵活的输出流创建自定义的 ostream / streambuf 类?
我发现了很多关于输出重定向、创建流缓冲区和 ostream 类的信息,但我还没有成功地应用它来达到我的目的。这篇文章变得相当冗长,因为我想描述我的逐步方法。
我有一个应用程序,它使用 MyNotifier 类来捕获应用程序中的事件并根据事件数据撰写日志消息。默认情况下,它将日志消息发送到 std::cout,但 MyNotifier 的构造函数接受 std::ostream& 类型的变量。来覆盖这个。我正在尝试构造一个该类型的类,该类应该将日志发送到不同的输出通道,例如通过 MQTT 客户端。我已经启动并运行良好的 MQTT。我的问题是关于自定义 ostream 类的创建。
这是应该使用新类的代码(请参阅 app_main 中的注释行)以及当我使用 std::cout 时的输出。为了进行测试,事件是通过直接调用 MyNotifier::Notify 生成的。
class MyNotifier {
public:
//constructor
MyNotifier(std::ostream& os = std::cout) : ost(os) {}
//takes eventdata as a code for the event
//composes some output string based on the input and outputs it to the customizable output stream ost
virtual void Notify( unsigned long eventdata);
protected:
std::ostream& ost;
}; //class MyNotifier
Implementation:
void MyNotifier::Notify(unsigned long eventdata) {
//takes eventdata as dummy for an event
//composes some output string based on the input and outputs it to the customizable output stream ost
char s[200];
int wr = sprintf(s, "RECEIVED EVENT %s ", "of type 1 ");
sprintf( s + wr , "with number %lu\n", eventdata);
std::cout << "MyNotifier::Notify" << s << std::endl; //this shows up
ost << "dummy custom_ostream output: " << eventdata << std::endl;
//trial to send over MQTT, in the end ost should generate MQTT output
esp_mqtt_client_publish(mqtt_client, "/fckx_seq/GUI", "value", 0, 1, 0); //works fine
} //MyNotifier::Notify
void app_main(void) {
MyNotifier notifier; //instantiate with default output stream (std::cout)
//MyNotifier notifier(std::cout); //instantiate with default output stream explicitly, also works with the same result
//MyNotifier notifier(custom_ostream) //desired way to send logs over a Custom_ostream object
notifier.Notify(3142727); //notify some event
}
这通过 cout 给出了所需的输出:
类型 1 的接收事件,编号为 3142727
在自定义输出的第一步中,我仅自定义了 Streambuf 类 (OutStreamBuf)。它由“普通”ostream 类使用:
class OutStreamBuf : public std::streambuf {
protected:
/* central output function
* - print characters in uppercase mode
*/
//converts each character to uppercase
virtual int_type overflow (int_type c) {
if (c != EOF) {
// convert lowercase to uppercase
c = std::toupper(static_cast<char>(c),getloc());
//output to standard output
putchar(c);
}
return c;
}
// write multiple characters MUST USE CONST CHAR* ?
virtual std::streamsize xsputn (char* s, std::streamsize num) {
std::cout << "**size: " << num << std::endl;
std::cout << "OutStreamBuf contents: " << s << std::endl;
return 1;
}
}; //OutStreamBuf
Implementation:
OutStreamBuf outStreamBuf;
std::ostream custom_ostream(&outStreamBuf);
MyNotifier notifier(custom_ostream); //instantiate with customized output stream
notifier.Notify(314132345); //notify some event
custom_ostream << "Test << operator" << std::endl;
Output:
**MyNotifier::Notify direct: RECEIVED EVENT of type 1 with number 314132345
DUMMY CUSTOM_OSTREAM OUTPUT: 314132345 <------ THIS IS THE DESIRED OUTPUT
TEST << OPERATOR**
In my second step I want to get hold of the buffer contents to be able to forward this to my MQTT handler. So I decided that I need a customized ostream object. In the second trial I therefore created a customized ostream class (OutStream) with an *embedded* customized streambuf class:
class OutStream : public std::ostream {
private:
//private local Outbuf for OutStream
class Outbuf : public std::streambuf {
protected:
/* central output function
* - print characters in uppercase mode
*/
//converts each character to uppercase
virtual int_type overflow (int_type c) {
if (c != EOF) {
// convert lowercase to uppercase
c = std::toupper(static_cast<char>(c),getloc());
//output to standard output
putchar(c);
}
return c;
}
// write multiple characters MUST USE CONST CHAR* (?)
virtual std::streamsize xsputn (char* s, std::streamsize num) {
std::cout << "**size: " << num << std::endl;
std::cout << "OUTBUF contents: " << s << std::endl;
return 1;
}
}; //Outbuf
Outbuf outbuf;
std::streambuf * buf;
public:
//constructor
OutStream() {
//buf = this->rdbuf(); //compiles OK, but what does it do ?
buf = rdbuf(); //compiles OK, but what does it do ?
std::cout << "SOME MESSAGE FROM OutStream constructor" <<std::endl;
esp_mqtt_client_publish(mqtt_client, "/fckx_seq/GUI", "OutStream constructor",
, 1, 0);
};
// << operator
//https://www.geeksforgeeks.org/overloading-stream-insertion-operators-c/
//have a good look on what parameters the operator should take , see the above article
friend std::ostream & operator << (std::ostream &stream, const OutStream& outStream){
esp_mqtt_client_publish(mqtt_client, "/fckx_seq/GUI", "OutStream << operator", 0, 1, 0); //doesn't show
stream << "Test << operator inside " << std::endl; //doesn't show
return stream; //return the stream
};
}; //OutStream
Implementation:
``` OutStream custom_ostream; //using a composite ostream/streambuf object
MyNotifier notifier(custom_ostream); //instantiate with customized output stream
notifier.Notify(314132345); //notify some event
custom_ostream << "Test << operator" << std::endl;
这不显示定制的输出。因此,我在构造函数中添加了一个日志(正确显示)和一个修改后的 <<带有日志的运算符(也未显示):
SOME MESSAGE FROM OutStream 构造函数
MyNotifier::Notify direct: RECEIVED EVENT of type 1 with number 314132345
操作员日志也失败我认为 ostream 对象的构造函数和/或它与streambuf 的绑定有问题。这对我来说是相当复杂的事情。一些帮助将不胜感激。
[编辑] 在与 Stephen M. Webb 讨论后,我集中精力寻找一个基于 std::streambuf 的包含额外缓冲的类的示例。我发现以下代码有望成为进一步步骤的良好基础:
//p. 837 The C++ Standard Library Second Edition, Nicolai M. Josuttis
class Outbuf_buffered : public std::streambuf {
protected:
static const int bufferSize = 10; // size of data buffer
char buffer[bufferSize]; // data buffer
public:
// constructor
// - initialize data buffer
// - one character less to let the bufferSizeth character cause a call of overflow()
Outbuf_buffered() {
setp (buffer, buffer+(bufferSize-1));
}
// destructor
// - flush data buffer
virtual ~Outbuf_buffered() {
sync();
}
protected:
// flush the characters in the buffer
int flushBuffer () {
int num = pptr()-pbase();
if (write (1, buffer, num) != num) {
return EOF;
}
pbump (-num); // reset put pointer accordingly
return num;
}
// buffer full
// - write c and all previous characters
virtual int_type overflow (int_type c) {
if (c != EOF) {
// insert character into the buffer
*pptr() = c;
pbump(1);
}
// flush the buffer
if (flushBuffer() == EOF) {
// ERROR
return EOF;
}
return c;
}
// synchronize data with file/destination
// - flush the data in the buffer
virtual int sync () {
if (flushBuffer() == EOF) {
// ERROR
return -1;
}
return 0;
}
}; //Outbuf_buffered
I found quite some info on output redirection, creation of streambuffers and ostream classes, but I did not manage to apply this succesfully yet for my purpose. This post has become quite lengthy because I wanted to describe my step by step approach.
I have an application that uses a class MyNotifier that captures events in the application and composes log messages based on the event data. By default it sends the log messages to std::cout, but the constructor of MyNotifier accepts a variable of type std::ostream& to overide this. I am trying to construct a class of that type which should send the logs to an different output channel, e.g. via an MQTT client. I have MQTT up and running well. My question is about the creation of the custom ostream class.
Here is the code that should use the new class (see the commented lines in app_main) and it's output when I use std::cout. For testing, the events are generated by calling MyNotifier::Notify directly.
class MyNotifier {
public:
//constructor
MyNotifier(std::ostream& os = std::cout) : ost(os) {}
//takes eventdata as a code for the event
//composes some output string based on the input and outputs it to the customizable output stream ost
virtual void Notify( unsigned long eventdata);
protected:
std::ostream& ost;
}; //class MyNotifier
Implementation:
void MyNotifier::Notify(unsigned long eventdata) {
//takes eventdata as dummy for an event
//composes some output string based on the input and outputs it to the customizable output stream ost
char s[200];
int wr = sprintf(s, "RECEIVED EVENT %s ", "of type 1 ");
sprintf( s + wr , "with number %lu\n", eventdata);
std::cout << "MyNotifier::Notify" << s << std::endl; //this shows up
ost << "dummy custom_ostream output: " << eventdata << std::endl;
//trial to send over MQTT, in the end ost should generate MQTT output
esp_mqtt_client_publish(mqtt_client, "/fckx_seq/GUI", "value", 0, 1, 0); //works fine
} //MyNotifier::Notify
void app_main(void) {
MyNotifier notifier; //instantiate with default output stream (std::cout)
//MyNotifier notifier(std::cout); //instantiate with default output stream explicitly, also works with the same result
//MyNotifier notifier(custom_ostream) //desired way to send logs over a Custom_ostream object
notifier.Notify(3142727); //notify some event
}
This gives the desired output over cout:
RECEIVED EVENT of type 1 with number 3142727
In my first step to customize the output I only customize the streambuf class (OutStreamBuf). It is used by a "plain" ostream class:
class OutStreamBuf : public std::streambuf {
protected:
/* central output function
* - print characters in uppercase mode
*/
//converts each character to uppercase
virtual int_type overflow (int_type c) {
if (c != EOF) {
// convert lowercase to uppercase
c = std::toupper(static_cast<char>(c),getloc());
//output to standard output
putchar(c);
}
return c;
}
// write multiple characters MUST USE CONST CHAR* ?
virtual std::streamsize xsputn (char* s, std::streamsize num) {
std::cout << "**size: " << num << std::endl;
std::cout << "OutStreamBuf contents: " << s << std::endl;
return 1;
}
}; //OutStreamBuf
Implementation:
OutStreamBuf outStreamBuf;
std::ostream custom_ostream(&outStreamBuf);
MyNotifier notifier(custom_ostream); //instantiate with customized output stream
notifier.Notify(314132345); //notify some event
custom_ostream << "Test << operator" << std::endl;
Output:
**MyNotifier::Notify direct: RECEIVED EVENT of type 1 with number 314132345
DUMMY CUSTOM_OSTREAM OUTPUT: 314132345 <------ THIS IS THE DESIRED OUTPUT
TEST << OPERATOR**
In my second step I want to get hold of the buffer contents to be able to forward this to my MQTT handler. So I decided that I need a customized ostream object. In the second trial I therefore created a customized ostream class (OutStream) with an *embedded* customized streambuf class:
class OutStream : public std::ostream {
private:
//private local Outbuf for OutStream
class Outbuf : public std::streambuf {
protected:
/* central output function
* - print characters in uppercase mode
*/
//converts each character to uppercase
virtual int_type overflow (int_type c) {
if (c != EOF) {
// convert lowercase to uppercase
c = std::toupper(static_cast<char>(c),getloc());
//output to standard output
putchar(c);
}
return c;
}
// write multiple characters MUST USE CONST CHAR* (?)
virtual std::streamsize xsputn (char* s, std::streamsize num) {
std::cout << "**size: " << num << std::endl;
std::cout << "OUTBUF contents: " << s << std::endl;
return 1;
}
}; //Outbuf
Outbuf outbuf;
std::streambuf * buf;
public:
//constructor
OutStream() {
//buf = this->rdbuf(); //compiles OK, but what does it do ?
buf = rdbuf(); //compiles OK, but what does it do ?
std::cout << "SOME MESSAGE FROM OutStream constructor" <<std::endl;
esp_mqtt_client_publish(mqtt_client, "/fckx_seq/GUI", "OutStream constructor",
, 1, 0);
};
// << operator
//https://www.geeksforgeeks.org/overloading-stream-insertion-operators-c/
//have a good look on what parameters the operator should take , see the above article
friend std::ostream & operator << (std::ostream &stream, const OutStream& outStream){
esp_mqtt_client_publish(mqtt_client, "/fckx_seq/GUI", "OutStream << operator", 0, 1, 0); //doesn't show
stream << "Test << operator inside " << std::endl; //doesn't show
return stream; //return the stream
};
}; //OutStream
Implementation:
``` OutStream custom_ostream; //using a composite ostream/streambuf object
MyNotifier notifier(custom_ostream); //instantiate with customized output stream
notifier.Notify(314132345); //notify some event
custom_ostream << "Test << operator" << std::endl;
This does not show the customised output. Therefore I added a log in the constructor (properly shown) and a modified << operator with a log (also not shown):
SOME MESSAGE FROM OutStream constructor
MyNotifier::Notify direct: RECEIVED EVENT of type 1 with number 314132345
As the << operator log also fails I think that something is wrong with the constructor of the ostream object and/or it's binding with the streambuf. This is pretty complex stuff for me. Some help would be appreciated.
[EDIT] After discussion with Stephen M. Webb I focused on finding an example of a class based on std::streambuf that contains additional buffering. I found the following code that will hopefully be a good basis for further steps:
//p. 837 The C++ Standard Library Second Edition, Nicolai M. Josuttis
class Outbuf_buffered : public std::streambuf {
protected:
static const int bufferSize = 10; // size of data buffer
char buffer[bufferSize]; // data buffer
public:
// constructor
// - initialize data buffer
// - one character less to let the bufferSizeth character cause a call of overflow()
Outbuf_buffered() {
setp (buffer, buffer+(bufferSize-1));
}
// destructor
// - flush data buffer
virtual ~Outbuf_buffered() {
sync();
}
protected:
// flush the characters in the buffer
int flushBuffer () {
int num = pptr()-pbase();
if (write (1, buffer, num) != num) {
return EOF;
}
pbump (-num); // reset put pointer accordingly
return num;
}
// buffer full
// - write c and all previous characters
virtual int_type overflow (int_type c) {
if (c != EOF) {
// insert character into the buffer
*pptr() = c;
pbump(1);
}
// flush the buffer
if (flushBuffer() == EOF) {
// ERROR
return EOF;
}
return c;
}
// synchronize data with file/destination
// - flush the data in the buffer
virtual int sync () {
if (flushBuffer() == EOF) {
// ERROR
return -1;
}
return 0;
}
}; //Outbuf_buffered
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您根本不想触及 ostream(格式化层)。您应该在streambuf(传输层)中执行所有操作。如果需要,您可以使用操纵器通过通用 ostream 接口设置或更改底层传输层的状态。
You don't want to touch ostream (the formatting layer) at all. You should do everything in the streambuf (the transport layer). You can use manipulators to set or change the state of the underlying transport layers through the generic ostream interface, if required.
通过使用从 std::streambuf 派生的包含附加缓冲区成员的类,我可以做到这一点。主要的 ostream 输出流只是 std::ostream。我从《C++ 标准库第二版》中找到了一个示例,Nicolai M. Josuttis,第 14 页。 837.
请参阅我原来的帖子中的编辑。
感谢@Stephen M. Webb 坚持不懈地指导我找到答案!
By using a class that is derived from std::streambuf that contains an additional buffer member I can do the trick. The main ostream output stream is just std::ostream. I found an example from The C++ Standard Library Second Edition, Nicolai M. Josuttis, p. 837 .
See the EDIT in my original post.
Thanks @Stephen M. Webb for your perseverance in guiding me to the answer!