Inheriting from std::basic_streambuf to write to a socket
Asked Answered
C

1

15

I would like to write a logging library of my own that provides abstraction for wherever the log entries are sent to.

The IO library of C++ already provides that kind of abstraction with std::stringstream and std::fstream. I would also like to be able to read/write from/to a socket.

I read that the proper way of extending the standard library is to inherit from std::basic_streambuf. What I don't understand is, if inheriting from std::basic_streambuf like std::basic_filebuf does, where is the need for the std::ifsream, std::ofstream and std::fstream classes ? Can't I just replace the buffer of some stream with a instance of a subclass of std::basic_streambuf which outputs where I want it to ?

So far I have done the following, but I really am not sure about what I'm doing. Is the following design correct ?

template< typename char_type, typename traits_type = std::char_traits< char_type > >
class basic_sock_streambuf : public std::basic_streambuf< char_type, traits_type >
{
public:

    basic_sock_streambuf()
    {

    }

    ~basic_sock_streambuf()
    {

    }

    int overflow (int c = EOF)
    {
        fputc( c, stdout ); // Temporary.
        return traits_type::to_int_type( c );
    }

    int underflow()
    {
        return fgetc( stdout ); // Temporary.
    }

    int sync()
    {
        return 0;
    }
};

template< typename char_type, typename traits_type = std::char_traits< char_type > >
class basic_isockstream : public std::basic_istream< char_type, traits_type >
{
};

template< typename char_type, typename traits_type = std::char_traits< char_type > >
class basic_osockstream : public std::basic_ostream< char_type, traits_type >
{
};

template< typename char_type, typename traits_type = std::char_traits< char_type > >
class basic_socktream : public basic_isockstream< char_type, traits_type >, public basic_osockstream< char_type, traits_type >
{
private:

    typedef basic_isockstream< char_type, traits_type > iparent;

    typedef basic_osockstream< char_type, traits_type > oparent;

    basic_sock_streambuf< char_type, traits_type > sock_sb;

    std::basic_streambuf< char_type, traits_type > * old_isb;

    std::basic_streambuf< char_type, traits_type > * old_osb;

public:

    basic_socktream()
    {
        old_isb = iparent::rdbuf( & sock_sb );
        old_osb = oparent::rdbuf( & sock_sb );
    }

    ~basic_socktream() throw()
    {
        iparent::rdbuf( old_isb );
        oparent::rdbuf( old_osb );
    }
};

EDIT : Code updated based on answers :

template<
    typename char_type,
    typename traits_type = std::char_traits< char_type > >
class basic_sockbuf :
    public std::basic_streambuf< char_type, traits_type >
{
public:

    basic_sockbuf()
    {
    }

    ~basic_sockbuf()
    {
    }

    int overflow( int c = EOF )
    {
        fputc( c, stdout ); // Temporary.
        return traits_type::to_int_type( c );
    }

    int underflow()
    {
        return fgetc( stdout ); // Temporary.
    }

    int sync()
    {
        return 0;
    }
};

template<
    typename char_type,
    typename traits_type = std::char_traits< char_type > >
class basic_isockstream :
    public std::basic_istream< char_type, traits_type >
{
private:

    typedef std::basic_istream< char_type, traits_type > parent;

    basic_sockbuf< char_type, traits_type > buffer;

public:

    basic_isockstream() :
        std::basic_istream< char_type, traits_type >::basic_istream(),
        buffer()
    {
        init( & buffer );
    }
};

template<
    typename char_type,
    typename traits_type = std::char_traits< char_type > >
class basic_osockstream :
    public std::basic_ostream< char_type, traits_type >
{
private:

    basic_sockbuf< char_type, traits_type > buffer;

public:

    basic_osockstream() :
        std::basic_ostream< char_type, traits_type >::basic_istream(),
        buffer()
    {
        init( & buffer );
    }
};

template<
    typename char_type,
    typename traits_type = std::char_traits< char_type > >
class basic_socktream :
    public std::basic_iostream< char_type, traits_type >,
    public basic_sockbuf< char_type, traits_type >
{
private:

    basic_sockbuf< char_type, traits_type > buffer;

public:

    basic_socktream() :
        std::basic_iostream< char_type, traits_type >::basic_iostream(),
        buffer()
    {
        std::basic_iostream< char_type, traits_type >::init( & buffer );
    }
};
Conditional answered 20/1, 2017 at 23:14 Comment(3)
Good news, this is actually pretty easy to do.Gers
Nice. What about writing some buffer at once?Harrold
read / write for buffers goto streambuf::xsget() , -xsput() which is the typical access for socket streamsJulienne
B
9

std::istream and std::ostream provide formatted input and output operations. That is, converting the stream to/from numbers, strings, etc...

std::basic_streambuf is a lower-level interface that reads or writes chunks of characters to or from ...somewhere. That's what you need to subclass and implement.

And, you're on the right track. Both std::istream and std::ostream have an overloaded constructor that take a pointer to a stream buffer. So, your plan of action is:

  1. Subclass and implement your custom std::basic_streambuf.

  2. Construct a std::istream or a std::ostream using a pointer to your stream buffer.

Can't I just replace the buffer of some stream with a instance of a subclass of std::basic_streambuf

No, not replace, but construct one. You construct a std::istream or a std::ostream, using a pointer to your buffer. You will not use a std::[io]fstream, but rather std::istream and std::ostream, constructed using your stream buffer.

All that a std::ifstream is, for example, is a subclass of std::istream that constructs its superclass with a pointer to an internal stream buffer that reads from a file.

Feel free to create your own subclass of std::istream, that multiply-inherits from your stream buffer subclass, and std::istream, constructs the stream buffer subclass first, then the std::istream.

Busman answered 20/1, 2017 at 23:22 Comment(8)
Thanks for your help. Please see my edit. Is this correct ? Here I will have two buffers, one in each base class of my basic_sockstream class. Is that correct ? Why having a buffer for input and another one for output when a single buffer supports both operations ? Also one more question : the data formatting (string/number conversions, etc) is independent from where the data is sent to. So why isn't there a single "top level" stream class that would use different streambuf implementations instead of having multiple "top level" stream classes like stringstream and fstream ?Conditional
Not quite correct. The superclass gets constructed first, using a pointer to a class member that's not constructed yet. The superclass gets constructed before the class member. Although this is unlikely to cause any actual problem, this is technically a bug, and is undefined behavior. There's a reason I referenced multiple inheritance -- you should inherit from your stream buffer first, then the stream class, so that the stream buffer gets constructed first. And, yes, you can -- and you should -- use a single stream buffer for both input and output operations.Busman
Now I'm not sure about how to do this. Where would you place the streambuf ? In the "top level" basic_sockstream class ? In the constructor of basic_sockstream, I first have to call parent constructors and provide them with a streambuf, which I do not have at that point, to be forwarded to the constructors of basic_istream and basic_ostream. On the other end, I if put that buffer into basic_isockstream and basic_osockstream, I will end up having two buffers.Conditional
As I explained: multiple inheritance. Inherit from both streambuf, and std::ostream. class basic_osockstream : public basic_sock_streambuf< ... >, public std::basic_ostream< char_type, traits_type >. Constructor: basic_osockstream(): parent(this).Busman
I see. But then, just like fstream inherits from ifstream and ofstream, If basic_sockstream inherits from the basic_osockstream you just described, and also from an equivalent basic_isockstream (which would symetrically inherit from basic_sock_streambuf and basic_istream), I will get two buffers, wouldn't I ?Conditional
Well I just checked the definition of basic_fstream and it inherits from iostream (and not just ostream) and stream_fieldbuf , so I guess I understand now.Conditional
I have updated my code in the question. I think it is correct now. Can you please mention the inheritance from basic_iostream and basic_streambuf in your answer ?Conditional
Multiple inheritance of this kind is one way to do it, but not the only one. The C++ standard does not require std::fstream to inherit from std::basic_filebuf, only from std::iostream. Individual C++ implementations have their own means of correctly piecing together a jigsaw puzzle. You, on the other hand, are free to glue together your classes in whichever way makes sense for you, and the approach involving multiple inheritance is simply the easiest one.Busman

© 2022 - 2024 — McMap. All rights reserved.