4
\$\begingroup\$

I am writing a plugin in C++ that uses C API that gives me a single logging function with this signature:

typedef void(*println_t)(const char* text);

I want to wrap this type of a function in a nice ostream to use all C++ formatting features. Having read it is best to use streambuf for this, I made this code:

//println_buf.h

#include <string>
#include <sstream>

class println_buf : public std::streambuf
{
    typedef void(*println_t)(const char* text);
    println_t logfunc_;

    std::string buffer;
public:
    println_buf(println_t logfunc) : logfunc_(logfunc)
    {

    }

    println_buf(const println_buf &) = delete;
    println_buf(println_buf &&);
    println_buf& operator=(const println_buf &) = delete;
    println_buf& operator=(println_buf &&);

    virtual std::streamsize xsputn(const char* s, std::streamsize n) override;
    virtual int overflow(int c) override;
    virtual int sync() override;
};

 

//println_buf.cpp

#include "println_buf.h"

using namespace std;

println_buf::println_buf(println_buf &&o) : logfunc_(o.logfunc_), buffer(std::move(o.buffer))
{

}

println_buf& println_buf::operator=(println_buf &&o)
{
    logfunc_ = o.logfunc_;
    buffer = std::move(o.buffer);
    return *this;
}

streamsize println_buf::xsputn(const char* s, streamsize n)
{
    streamsize start = 0;
    for (std::streamsize i = 0; i < n; i++)
    {
        if (s[i] == '\n')
        {
            buffer.append(s + start, s + i);
            sync();
            start = i + 1;
        }
    }
    buffer.append(s + start, s + n);
    return n;
}

int println_buf::overflow(int c)
{
    char ch = traits_type::to_char_type(c);
    if (ch == '\n')
    {
        sync();
    } else {
        buffer.append(1, c);
    }
    return c;
}

int println_buf::sync()
{
    logfunc_(buffer.c_str());
    buffer.clear();
    return 0;
}

Usage:

println_t logfunc = ...;
println_buf buffer(logfunc);
std::ostream stream(&buffer);
stream << "Hello world" << std::endl;

It should be noted that println_t includes a newline automatically in the actual output, but I want to have to specify the line separator in C++.

My concern is that the function should not be called with a string containing \n, but the string must be passed to the function only if it originally ended with \n, and be buffered otherwise. Also endl should cause the function to be called immediately.

\$\endgroup\$
1
\$\begingroup\$

I'll review more later but a basic comment first.

Rather than write just the println_buf class I would also write the println_stream class that internally has a println_buf.

class println_stream: std::ostream
{
    // Make this a private class.
    // You don't need to leak implementation details.
    // As far as anybody else is convered it is just std::streambuf
    class println_buf { /* STUFF */ };

    println_buf     buf;
    std::streambuf* oldBuf = nullptr;
    public:
        println_stream()
            // You can't pass the buf to the ostream constructor
            // here as it has not been constructed yet.
            // There are ways but lets keep it simple for now.
        {
            // I did not do the work with logfunc
            // Have not read that bit yet.
            // But you get the idea I hope.
            oldBuf = rdbuf(&buf);
        }
        ~println_stream()
        {
            // Can't remember if it is required.
            // But I would always set back the original buffer.
            rdbuf(oldBuf);
        }
};

int main()
{
   println_stream stream;
    stream << "Hello world\n";
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.