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

How to create customized ostream / streambuf classes for flexible output stream?

提问人:f ckx 提问时间:3/24/2022 最后编辑:f ckx 更新时间:3/27/2022 访问量:915

问:

我发现了很多关于输出重定向、创建流缓冲区和 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 的 RECEIVED 事件,编号为 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;

这不会显示自定义输出。因此,我在构造函数中添加了一个日志(正确显示)和一个带有日志的修改后的 << 运算符(也未显示):

来自 OutStream 构造函数的一些消息

MyNotifier::Notify direct:类型 1 的 RECEIVED 事件,编号为 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
C++ ostream streambuf

评论

0赞 f ckx 3/24/2022
对不起,格式不好。我尝试了一个很好的尝试,以摆脱代码部分上的编辑器警告。最后,该帖子被接受,但有一些缺陷......

答:

2赞 Stephen M. Webb 3/24/2022 #1

您根本不想触摸 ostream(格式层)。您应该在 streambuf(传输层)中执行所有操作。如果需要,可以使用操纵器通过通用 ostream 接口设置或更改基础传输层的状态。

评论

0赞 f ckx 3/24/2022
这意味着我必须从我的第一步开始(未触及的 ostream,自定义的 streambuf)。您能否提示我如何捕获那里的完整流内容以撰写 MQTT(或其他)消息。我推测,当前输出是通过 OutStreamBuf 中的溢出一次生成一个字符的。
3赞 Stephen M. Webb 3/25/2022
标准 ostream 调用其包含的 streambuf 对象的成员函数,该对象应将字符串追加到其缓冲区。到时候,streambuf 对象将调用其成员函数将整个缓冲区刷新到其目标(例如 call .您可能想检查 std::basic_filebuf 如何在您最喜欢的 C++ 标准库实现上工作。你可能想要派生你的 streambufsputn()overflow()esp_mqtt_client_publish()
0赞 f ckx 3/26/2022
感谢您的评论!您能否提示我为什么我现在拥有的代码根本没有输出通过 << 运算符输入的任何内容(ost << “dummy custom_ostream output: ” << eventdata << std::endl;在 MyNotifier::Notify 中)。构造函数有问题吗?我知道调用构造函数是因为它输出“来自 OutStream 构造函数的一些消息”。
1赞 Stephen M. Webb 3/26/2022
你的 streambuf 需要缓冲它的输出,只有当 xsputn() 检测到缓冲区已满时,它才应该调用 overflow()——也就是说,如果再添加一个字符,缓冲区就会溢出。overflow() 成员函数应将整个缓冲区刷新到其接收器,并重置所有内部缓冲区指针。
1赞 Stephen M. Webb 3/26/2022
std::streambuf 类负责为您缓冲流 -- 这解释了它的名称。您只需要提供来自 overflow() 的写入操作并重置指针。您可以使用 pbase() 和 pptr() 函数获取缓冲区指针,并使用 setp() 函数重置它们。这一切都用一个狡猾的图表很好地记录在 en.cppreference.com/w/cpp/io/basic_streambuf
0赞 f ckx 3/27/2022 #2

通过使用派生自 std::streambuf 的类,该类包含额外的缓冲区成员,我可以做到这一点。主 ostream 输出流只是 std::ostream。我从 C++ 标准库第二版中找到了一个示例,Nicolai M. Josuttis,第 837 页。

请参阅我原始帖子中的编辑。 感谢 @Stephen M. Webb 坚持不懈地引导我找到答案!

评论

0赞 f ckx 3/28/2022
我仍然无法将附加缓冲区的内容正确放入我的输出接收器(MQTT 消息)中。显示的代码无法正确执行此操作。尽管这需要更好地理解处理缓冲区及其指针,但我认为这超出了原始问题的范围。
0赞 ankostis 7/16/2022
我建议更新上面的回复,以列出到目前为止您的解决方案的完整来源(而不是将其附加在开场白 (OP) 的底部。