如何为灵活的输出流创建自定义的 ostream / streambuf 类?

发布于 2025-01-16 14:05:17 字数 8430 浏览 4 评论 0原文

我发现了很多关于输出重定向、创建流缓冲区和 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

青瓷清茶倾城歌 2025-01-23 14:05:17

您根本不想触及 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.

若有似无的小暗淡 2025-01-23 14:05:17

通过使用从 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!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文